कई पंक्तियों से एकल पंक्ति में स्तंभ मिलाएं


14

मुझे customer_commentsडेटाबेस डिज़ाइन के कारण कई पंक्तियों में विभाजित किया गया है , और एक रिपोर्ट के लिए मुझे commentsप्रत्येक अद्वितीय idको एक पंक्ति में संयोजित करने की आवश्यकता है । मैंने पहले सिलेक्टेड क्लॉज और COALESCE ट्रिक से इस सीमांकित सूची के साथ काम करने की कोशिश की, लेकिन मैं इसे याद नहीं कर सकता और इसे सहेजना नहीं चाहिए था। मैं इस मामले में काम करने के लिए इसे प्राप्त करने के लिए प्रतीत नहीं हो सकता है, केवल एक पंक्ति पर काम करने लगता है।

डेटा इस तरह दिखता है:

id  row_num  customer_code comments
-----------------------------------
1   1        Dilbert        Hard
1   2        Dilbert        Worker
2   1        Wally          Lazy

मेरे परिणामों को इस तरह देखना होगा:

id  customer_code comments
------------------------------
1   Dilbert        Hard Worker
2   Wally          Lazy

इसलिए प्रत्येक row_numके लिए वास्तव में परिणामों की केवल एक पंक्ति है; के क्रम में टिप्पणियों को जोड़ा जाना चाहिए row_num। उपरोक्त लिंक की गई SELECTट्रिक एक विशिष्ट पंक्ति के लिए सभी मानों को एक पंक्ति के रूप में प्राप्त करने के लिए काम करती है, लेकिन मैं यह पता नहीं लगा सकता कि इसे SELECTइन सभी पंक्तियों से बाहर निकालने वाले कथन के भाग के रूप में कैसे काम किया जाए ।

मेरी क्वेरी को अपने आप पूरी तालिका से गुजरना होगा और इन पंक्तियों को आउटपुट करना होगा। मैं उन्हें कई स्तंभों में जोड़ रहा हूं, प्रत्येक पंक्ति के लिए एक, इसलिए PIVOTलागू नहीं होता है।

जवाबों:


18

यह एक सहसंबद्ध उपशम के साथ करने के लिए अपेक्षाकृत तुच्छ है। जब तक आप उपयोगकर्ता-परिभाषित फ़ंक्शन (या जब तक आप केवल एक पंक्ति को एक बार में वापस नहीं करना चाहते) नहीं निकालते, तब तक आपके द्वारा उल्लिखित ब्लॉग पोस्ट में उल्लिखित COALESCE पद्धति का उपयोग नहीं किया जा सकता है। यहां बताया गया है कि मैं आमतौर पर ऐसा कैसे करता हूं:

DECLARE @x TABLE 
(
  id INT, 
  row_num INT, 
  customer_code VARCHAR(32), 
  comments VARCHAR(32)
);

INSERT @x SELECT 1,1,'Dilbert','Hard'
UNION ALL SELECT 1,2,'Dilbert','Worker'
UNION ALL SELECT 2,1,'Wally','Lazy';

SELECT id, customer_code, comments = STUFF((SELECT ' ' + comments 
    FROM @x AS x2 WHERE id = x.id
     ORDER BY row_num
     FOR XML PATH('')), 1, 1, '')
FROM @x AS x
GROUP BY id, customer_code
ORDER BY id;

आप एक मामला है, तो जहां टिप्पणी में डेटा असुरक्षित के लिए एक्सएमएल वर्ण हो सकता है ( >, <, &), तो आप इस बदलना चाहिए:

     FOR XML PATH('')), 1, 1, '')

इस अधिक विस्तृत दृष्टिकोण के लिए:

     FOR XML PATH(''), TYPE).value(N'(./text())[1]', N'varchar(max)'), 1, 1, '')

(सही गंतव्य डेटा प्रकार, varcharया nvarchar, और सही लंबाई का उपयोग करना सुनिश्चित करें , और Nयदि उपयोग कर रहे हैं तो सभी स्ट्रिंग शाब्दिक उपसर्ग करें nvarchar।)


3
+ 1 मैंने एक तेज़ नज़र के लिए एक फील किया। sqlfiddle.com/#
-3/

3
हां, यह एक आकर्षण की तरह काम करता है। @MarlonRibunal एसक्यूएल फिडल वास्तव में आकार देने!
बेन ब्रोका

@NickChammas - मैं अपनी गर्दन बाहर छड़ी करने जा रहा हूं और कहता हूं कि order byउप क्वेरी में आदेश का उपयोग करने की गारंटी है । यह XML का उपयोग कर निर्माण कर रहा है for xmlऔर यह TSQL का उपयोग करके XML बनाने का तरीका है। XML फ़ाइलों में तत्वों का क्रम एक महत्वपूर्ण मामला है और इस पर भरोसा किया जा सकता है। इसलिए यदि यह तकनीक ऑर्डर की गारंटी नहीं देती है तो TSQL में XML समर्थन गंभीर रूप से टूट गया है।
मिकेल एरिकसन

2
मैंने पुष्टि की है कि अंतर्निहित तालिका पर क्लस्टर इंडेक्स की परवाह किए बिना क्वेरी सही क्रम में परिणाम लौटाएगी (यहां तक ​​कि एक क्लस्टर इंडेक्स को मिकेल के सुझाव के row_num descअनुसार पालन करना चाहिए order by)। मैं सुझाव देने वाली टिप्पणियों को हटाने जा रहा हूं अन्यथा अब क्वेरी में सही order byऔर आशा है कि @JonSeigel ऐसा करने पर विचार करता है।
हारून बर्ट्रेंड

6

यदि आपको अपने वातावरण में सीएलआर का उपयोग करने की अनुमति है, तो यह उपयोगकर्ता द्वारा परिभाषित कुल के लिए एक दर्जी मामला है।

विशेष रूप से, यह संभवतः जाने का तरीका है यदि स्रोत डेटा गैर-तुच्छ रूप से बड़ा है और / या आपको अपने आवेदन में इस प्रकार की बहुत कुछ करने की आवश्यकता है। मुझे दृढ़ता से संदेह है कि हारून के समाधान के लिए क्वेरी योजना अच्छी तरह से स्केल नहीं होगी क्योंकि इनपुट आकार बढ़ता है। (मैंने अस्थायी तालिका में एक इंडेक्स जोड़ने की कोशिश की, लेकिन इससे कोई मदद नहीं मिली।)

यह समाधान, कई अन्य चीजों की तरह, एक व्यापार है:

  • आपके या आपके ग्राहक के वातावरण में CLR एकीकरण का उपयोग करने के लिए राजनीति / नीति।
  • सीएलआर फ़ंक्शन तेजी से होने की संभावना है, और डेटा के एक वास्तविक सेट को बेहतर ढंग से मापेंगे।
  • सीएलआर फ़ंक्शन अन्य प्रश्नों में पुन: प्रयोज्य होगा, और आपको हर बार इस प्रकार की चीज़ करने के लिए एक जटिल उपकुंजी की नकल (और डिबग) नहीं करनी होगी।
  • स्ट्रेट टी-एसक्यूएल बाहरी कोड के एक टुकड़े को लिखने और प्रबंधित करने की तुलना में सरल है।
  • शायद आप नहीं जानते कि कैसे C # या VB में प्रोग्राम करें।
  • आदि।

संपादित करें: ठीक है, मैं यह देखने की कोशिश करने के लिए गया था कि क्या यह वास्तव में बेहतर था, और यह इस आवश्यकता को दर्शाता है कि टिप्पणी एक विशिष्ट क्रम में हो सकती है वर्तमान में एक समुच्चय फ़ंक्शन का उपयोग करके संतुष्ट करना संभव नहीं है। :(

SqlUserDefinedAggregateAttribute.IsInvariantToOrder देखें । मूल रूप से, आपको जो करने की आवश्यकता है वह है, OVER(PARTITION BY customer_code ORDER BY row_num)लेकिन समुच्चय करते समय खंड ORDER BYमें समर्थित नहीं है OVER। मैं मान रहा हूं कि SQL सर्वर में इस कार्यक्षमता को जोड़ने से कीड़े का एक कैन खुल जाता है, क्योंकि निष्पादन योजना में जो बदलाव करने की आवश्यकता होती है वह तुच्छ है। उपरोक्त लिंक का कहना है कि यह भविष्य के उपयोग के लिए आरक्षित है, इसलिए इसे भविष्य में लागू किया जा सकता है (2005 में आप शायद भाग्य से बाहर हैं, हालांकि)।

यह हो सकता है अभी भी पैकिंग और पार्स करने द्वारा पूरा किया जा row_numएकत्रित किया स्ट्रिंग में मूल्य, और उसके बाद प्रकार CLR वस्तु के भीतर ... जो सुंदर hackish लगता है कर।

किसी भी घटना में, नीचे दिया गया कोड मैं किसी और के उपयोग की स्थिति में भी इसे उपयोगी पाता है। मैं पाठक के लिए एक अभ्यास के रूप में हैकिंग भाग को छोड़ दूँगा। ध्यान दें कि मैंने परीक्षण डेटा के लिए एडवेंचरवर्क्स (2005) का उपयोग किया था।

एकत्रित विधानसभा:

using System;
using System.IO;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace MyCompany.SqlServer
{
    [Serializable]
    [SqlUserDefinedAggregate
    (
        Format.UserDefined,
        IsNullIfEmpty = false,
        IsInvariantToDuplicates = false,
        IsInvariantToNulls = true,
        IsInvariantToOrder = false,
        MaxByteSize = -1
    )]
    public class StringConcatAggregate : IBinarySerialize
    {
        private string _accum;
        private bool _isEmpty;

        public void Init()
        {
            _accum = string.Empty;
            _isEmpty = true;
        }

        public void Accumulate(SqlString value)
        {
            if (!value.IsNull)
            {
                if (!_isEmpty)
                    _accum += ' ';
                else
                    _isEmpty = false;

                _accum += value.Value;
            }
        }

        public void Merge(StringConcatAggregate value)
        {
            Accumulate(value.Terminate());
        }

        public SqlString Terminate()
        {
            return new SqlString(_accum);
        }

        public void Read(BinaryReader r)
        {
            this.Init();

            _accum = r.ReadString();
            _isEmpty = _accum.Length == 0;
        }

        public void Write(BinaryWriter w)
        {
            w.Write(_accum);
        }
    }
}

परीक्षण के लिए टी-एसक्यूएल ( CREATE ASSEMBLYऔर sp_configureसीएलआर को सक्षम करने के लिए):

CREATE TABLE [dbo].[Comments]
(
    CustomerCode int NOT NULL,
    RowNum int NOT NULL,
    Comments nvarchar(25) NOT NULL
)

INSERT INTO [dbo].[Comments](CustomerCode, RowNum, Comments)
    SELECT
        DENSE_RANK() OVER(ORDER BY FirstName),
        ROW_NUMBER() OVER(PARTITION BY FirstName ORDER BY ContactID),
        Phone
        FROM [AdventureWorks].[Person].[Contact]
GO

CREATE AGGREGATE [dbo].[StringConcatAggregate]
(
    @input nvarchar(MAX)
)
RETURNS nvarchar(MAX)
EXTERNAL NAME StringConcatAggregate.[MyCompany.SqlServer.StringConcatAggregate]
GO


SELECT
    CustomerCode,
    [dbo].[StringConcatAggregate](Comments) AS AllComments
    FROM [dbo].[Comments]
    GROUP BY CustomerCode

1

यहां एक कर्सर-आधारित समाधान है जो टिप्पणियों के क्रम की गारंटी देता है row_num। ( टेबल को कैसे आबाद किया गया , इसके लिए मेरा दूसरा जवाब देखें [dbo].[Comments]।)

SET NOCOUNT ON

DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
    SELECT
        CustomerCode,
        Comments
        FROM [dbo].[Comments]
        ORDER BY
            CustomerCode,
            RowNum

DECLARE @curCustomerCode int
DECLARE @lastCustomerCode int
DECLARE @curComment nvarchar(25)
DECLARE @comments nvarchar(MAX)

DECLARE @results table
(
    CustomerCode int NOT NULL,
    AllComments nvarchar(MAX) NOT NULL
)


OPEN cur

FETCH NEXT FROM cur INTO
    @curCustomerCode, @curComment

SET @lastCustomerCode = @curCustomerCode


WHILE @@FETCH_STATUS = 0
BEGIN

    IF (@lastCustomerCode != @curCustomerCode)
    BEGIN
        INSERT INTO @results(CustomerCode, AllComments)
            VALUES(@lastCustomerCode, @comments)

        SET @lastCustomerCode = @curCustomerCode
        SET @comments = NULL
    END

    IF (@comments IS NULL)
        SET @comments = @curComment
    ELSE
        SET @comments = @comments + N' ' + @curComment

    FETCH NEXT FROM cur INTO
        @curCustomerCode, @curComment

END

IF (@comments IS NOT NULL)
BEGIN
    INSERT INTO @results(CustomerCode, AllComments)
        VALUES(@curCustomerCode, @comments)
END

CLOSE cur
DEALLOCATE cur


SELECT * FROM @results

0
-- solution avoiding the cursor ...

DECLARE @idMax INT
DECLARE @idCtr INT
DECLARE @comment VARCHAR(150)

SELECT @idMax = MAX(id)
FROM [dbo].[CustomerCodeWithSeparateComments]

IF @idMax = 0
    return
DECLARE @OriginalTable AS Table
(
    [id] [int] NOT NULL,
    [row_num] [int] NULL,
    [customer_code] [varchar](50) NULL,
    [comment] [varchar](120) NULL
)

DECLARE @FinalTable AS Table
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [customer_code] [varchar](50) NULL,
    [comment] [varchar](120) NULL
)

INSERT INTO @FinalTable 
([customer_code])
SELECT [customer_code]
FROM [dbo].[CustomerCodeWithSeparateComments]
GROUP BY [customer_code]

INSERT INTO @OriginalTable
           ([id]
           ,[row_num]
           ,[customer_code]
           ,[comment])
SELECT [id]
      ,[row_num]
      ,[customer_code]
      ,[comment]
FROM [dbo].[CustomerCodeWithSeparateComments]
ORDER BY id, row_num

SET @idCtr = 1
SET @comment = ''

WHILE @idCtr < @idMax
BEGIN

    SELECT @comment = @comment + ' ' + comment
    FROM @OriginalTable 
    WHERE id = @idCtr
    UPDATE @FinalTable
       SET [comment] = @comment
    WHERE [id] = @idCtr 
    SET @idCtr = @idCtr + 1
    SET @comment = ''

END 

SELECT @comment = @comment + ' ' + comment
        FROM @OriginalTable 
        WHERE id = @idCtr

UPDATE @FinalTable
   SET [comment] = @comment
WHERE [id] = @idCtr

SELECT *
FROM @FinalTable

2
आपने एक कर्सर नहीं टाला है। आपने इसके बजाय अपने कर्सर को थोड़ी देर के लूप कहा है।
हारून बर्ट्रेंड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.