2011-02-08 19 views
5

कृपया प्रश्न की लंबाई क्षमा करें। मैंने स्थिति को प्रदर्शित करने के लिए एक परीक्षण स्क्रिप्ट और एक समाधान में मेरा सबसे अच्छा प्रयास शामिल किया।वाक्यांशों की पंक्तियों के लिए [प्रति पंक्ति एक शब्द] में शामिल हों [प्रति पंक्ति एकाधिक शब्द]

  1. test_WORDS = शब्द कई स्रोतों से क्रम में निकाले:

    दो तालिकाओं रहे हैं। OBJ_FK कॉलम स्रोत की आईडी है। WORD_ID शब्द के लिए एक पहचानकर्ता है जो स्रोत के भीतर अद्वितीय है। प्रत्येक पंक्ति में एक शब्द होता है।

  2. test_PHRASE = test_WORDS में खोजे जाने वाले वाक्यांशों की एक सूची। PHRASE_TEXT कॉलम एक स्पेस सेपरेटेड वाक्यांश है जैसे 'foo bar' (नीचे देखें) ताकि प्रत्येक पंक्ति में एकाधिक शब्द हों।

आवश्यकता: वापसी test_WORDS से पहला शब्द है कि एक test_PHRASE से एक वाक्यांश मिलान की शुरुआत है।

मैं नीचे आरबीएआर दृष्टिकोण से बचने के लिए कुछ सेट पसंद करूंगा। इसके अलावा मेरा समाधान 5 शब्द वाक्यांश तक ही सीमित है। मुझे 20 शब्द वाक्यांशों का समर्थन करने की आवश्यकता है। क्या में कर्सर के बिना test_WORD में संगत पंक्तियों के लिए पंक्तियों से मिलान करना संभव है?

एक अस्थायी तालिका में वाक्यांश शब्दों को तोड़ने के बाद, समस्या पंक्ति क्रम में दो सेटों के मिलान भागों के लिए उबलती है।

-- Create test data 
CREATE TABLE [dbo].[test_WORDS](
    [OBJ_FK] [bigint] NOT NULL,    --FK to the source object 
    [WORD_ID] [int] NOT NULL,    --The word order in the source object 
    [WORD_TEXT] [nvarchar](50) NOT NULL, 
    CONSTRAINT [PK_test_WORDS] PRIMARY KEY CLUSTERED 
    (
     [OBJ_FK] ASC, 
     [WORD_ID] ASC 
    ) 
) ON [PRIMARY]  
GO 

CREATE TABLE [dbo].[test_PHRASE](
    [ID] [int],  --PHRASE ID 
    [PHRASE_TEXT] [nvarchar](150) NOT NULL --Space-separated phrase 
    CONSTRAINT [PK_test_PHRASE] PRIMARY KEY CLUSTERED 
    (
     [ID] ASC 
    ) 
) 
GO 
INSERT INTO dbo.test_WORDS 
SELECT 1,1,'aaa' UNION ALL 
SELECT 1,2,'bbb' UNION ALL 
SELECT 1,3,'ccc' UNION ALL 
SELECT 1,4,'ddd' UNION ALL 
SELECT 1,5,'eee' UNION ALL 
SELECT 1,6,'fff' UNION ALL 
SELECT 1,7,'ggg' UNION ALL 
SELECT 1,8,'hhh' UNION ALL 
SELECT 2,1,'zzz' UNION ALL 
SELECT 2,2,'yyy' UNION ALL 
SELECT 2,3,'xxx' UNION ALL 
SELECT 2,4,'www' 

INSERT INTO dbo.test_PHRASE 
SELECT 1, 'bbb ccc ddd' UNION ALL --should match 
SELECT 2, 'ddd eee fff' UNION ALL --should match 
SELECT 3, 'xxx xxx xxx' UNION ALL --should NOT match 
SELECT 4, 'zzz yyy xxx' UNION ALL --should match 
SELECT 5, 'xxx www ppp' UNION ALL --should NOT match 
SELECT 6, 'zzz yyy xxx www' --should match 

-- Create variables 
DECLARE @maxRow AS INTEGER 
DECLARE @currentRow AS INTEGER 
DECLARE @phraseSubsetTable AS TABLE(
    [ROW] int IDENTITY(1,1) NOT NULL, 
    [ID] int NOT NULL,  --PHRASE ID 
    [PHRASE_TEXT] nvarchar(150) NOT NULL 
) 
--used to split the phrase into words 
--note: No permissions to sys.dm_fts_parser 
DECLARE @WordList table 
(
    ID int, 
    WORD nvarchar(50) 
) 
--Records to be returned to caller 
DECLARE @returnTable AS TABLE(
    OBJECT_FK INT NOT NULL, 
    WORD_ID INT NOT NULL, 
    PHRASE_ID INT NOT NULL 
) 
DECLARE @phrase AS NVARCHAR(150) 
DECLARE @phraseID AS INTEGER 

-- Get subset of phrases to simulate a join that would occur in production 
INSERT INTO @phraseSubsetTable 
SELECT ID, PHRASE_TEXT 
FROM dbo.test_PHRASE 
--represent subset of phrases caused by join in production 
WHERE ID IN (2,3,4) 

-- Loop each phrase in the subset, split into rows of words and return matches to the test_WORDS table 
SET @maxRow = @@ROWCOUNT 
SET @currentRow = 1 
WHILE @currentRow <= @maxRow 
BEGIN 
    SELECT @phrase=PHRASE_TEXT, @phraseID=ID FROM @phraseSubsetTable WHERE row = @currentRow 

    --clear previous phrase that was split into rows 
    DELETE FROM @WordList 

    --Recursive Function with CTE to create recordset of words, one per row 
    ;WITH Pieces(pn, start, stop) AS (
     SELECT 1, 1, CHARINDEX(' ', @phrase) 
     UNION ALL 
     SELECT pn + 1, stop + 1, CHARINDEX(' ', @phrase, stop + 1) 
     FROM Pieces 
     WHERE stop > 0) 
    --Create the List of words with the CTE above 
    insert into @WordList 
    SELECT pn, 
     SUBSTRING(@phrase, start, CASE WHEN stop > 0 THEN stop-start ELSE 1056 END) AS WORD 
    FROM Pieces 

    DECLARE @wordCt as int 
    select @wordCt=count(ID) from @WordList; 

    -- Do the actual query using a CTE with a rownumber that repeats for every SOURCE OBJECT 
;WITH WordOrder_CTE AS (
SELECT OBJ_FK, WORD_ID, WORD_TEXT, 
    ROW_NUMBER() OVER (Partition BY OBJ_FK ORDER BY WORD_ID) AS rownum 
FROM test_WORDS) 
--CREATE a flattened record of the first word in the phrase and join it to the rest of the words. 
INSERT INTO @returnTable 
SELECT r1.OBJ_FK, r1.WORD_ID, @phraseID AS PHRASE_ID 
FROM WordOrder_CTE r1 
INNER JOIN @WordList w1 ON r1.WORD_TEXT = w1.WORD and w1.ID=1 
LEFT JOIN WordOrder_CTE r2 
     ON r1.rownum = r2.rownum - 1 and r1.OBJ_FK = r2.OBJ_FK 
      LEFT JOIN @WordList w2 ON r2.WORD_TEXT = w2.WORD and w2.ID=2 
LEFT JOIN WordOrder_CTE r3 
     ON r1.rownum = r3.rownum - 2 and r1.OBJ_FK = r3.OBJ_FK 
      LEFT JOIN @WordList w3 ON r3.WORD_TEXT = w3.WORD and w3.ID=3 
LEFT JOIN WordOrder_CTE r4 
     ON r1.rownum = r4.rownum - 3 and r1.OBJ_FK = r4.OBJ_FK 
      LEFT JOIN @WordList w4 ON r4.WORD_TEXT = w4.WORD and w4.ID=4 
LEFT JOIN WordOrder_CTE r5 
     ON r1.rownum = r5.rownum - 4 and r1.OBJ_FK = r5.OBJ_FK 
      LEFT JOIN @WordList w5 ON r5.WORD_TEXT = w5.WORD and w5.ID=5 

WHERE (@wordCt < 2 OR w2.ID is not null) and 
     (@wordCt < 3 OR w3.ID is not null) and 
     (@wordCt < 4 OR w4.ID is not null) and 
     (@wordCt < 5 OR w5.ID is not null) 

    --loop 
    SET @currentRow = @currentRow+1 
END 

--Return the first words of each matching phrase 
SELECT OBJECT_FK, WORD_ID, PHRASE_ID FROM @returnTable 

GO 

--Clean up 
DROP TABLE [dbo].[test_WORDS] 
DROP TABLE [dbo].[test_PHRASE] 

संपादित समाधान:

यह गैर-निरंतर शब्द आईडी के लिए खाते के नीचे प्रदान की सही समाधान की एक संपादन है। उम्मीद है कि इससे किसी को भी उतना ही मदद मिलेगी जितना उसने किया था।

;WITH 
numberedwords AS (
    SELECT 
    OBJ_FK, 
    WORD_ID, 
    WORD_TEXT, 
    rowcnt = ROW_NUMBER() OVER 
     (PARTITION BY OBJ_FK ORDER BY WORD_ID DESC), 
    totalInSrc = COUNT(WORD_ID) OVER (PARTITION BY OBJ_FK) 
    FROM dbo.test_WORDS 
), 
phrasedwords AS (
    SELECT 
    nw1.OBJ_FK, 
    nw1.WORD_ID, 
    nw1.WORD_TEXT, 
    PHRASE_TEXT = RTRIM((
     SELECT [text()] = nw2.WORD_TEXT + ' ' 
     FROM numberedwords nw2 
     WHERE nw1.OBJ_FK = nw2.OBJ_FK 
     AND nw2.rowcnt BETWEEN nw1.rowcnt AND nw1.totalInSrc 
     ORDER BY nw2.OBJ_FK, nw2.WORD_ID 
     FOR XML PATH ('') 
    )) 
    FROM numberedwords nw1 
    GROUP BY nw1.OBJ_FK, nw1.WORD_ID, nw1.WORD_TEXT, nw1.rowcnt, nw1.totalInSrc 
) 
SELECT * 
FROM phrasedwords pw 
    INNER JOIN test_PHRASE tp 
    ON LEFT(pw.PHRASE_TEXT, LEN(tp.PHRASE_TEXT)) = tp.PHRASE_TEXT 
ORDER BY pw.OBJ_FK, pw.WORD_ID 

नोट: उत्पादन में उपयोग की जाने वाली अंतिम क्वेरी सीटीई के बजाय अनुक्रमित टेम्पलेट टेबल का उपयोग करती है। मैंने अपनी आवश्यकताओं के लिए PHRASE_TEXT कॉलम की लंबाई भी सीमित कर दी है। इन सुधारों के साथ, मैं अपने प्रश्न समय को 3 मिनट से 3 सेकंड तक कम करने में सक्षम था!

+0

"कृपया मुझे इससे बेहतर तरीका खोजने में मदद करें।" - मीट्रिक से बेहतर क्या है? –

+0

@ मिच: जब आप टिप्पणी कर रहे थे तो सवाल अपडेट किया जा रहा था। "मेरे समाधान के साथ समस्या ..." – Laramie

+3

यह वास्तव में ऐसा कुछ नहीं दिखता है जो आपको SQL –

उत्तर

3

यहाँ एक समाधान एक अलग दृष्टिकोण का उपयोग करता है है: शब्दों में वाक्यांशों बंटवारे के बजाय यह वाक्यांशों में शब्द को जोड़ती है।

संपादित: rowcnt अभिव्यक्ति COUNT(*) OVER … उपयोग करने के लिए, के रूप में टिप्पणी में @ErikE ने सुझाव दिया बदल दिया है।

;WITH 
numberedwords AS (
    SELECT 
    OBJ_FK, 
    WORD_ID, 
    WORD_TEXT, 
    rowcnt = COUNT(*) OVER (PARTITION BY OBJ_FK) 
    FROM dbo.test_WORDS 
), 
phrasedwords AS (
    SELECT 
    nw1.OBJ_FK, 
    nw1.WORD_ID, 
    nw1.WORD_TEXT, 
    PHRASE_TEXT = RTRIM((
     SELECT [text()] = nw2.WORD_TEXT + ' ' 
     FROM numberedwords nw2 
     WHERE nw1.OBJ_FK = nw2.OBJ_FK 
     AND nw2.WORD_ID BETWEEN nw1.WORD_ID AND nw1.rowcnt 
     ORDER BY nw2.OBJ_FK, nw2.WORD_ID 
     FOR XML PATH ('') 
    )) 
    FROM numberedwords nw1 
    GROUP BY nw1.OBJ_FK, nw1.WORD_ID, nw1.WORD_TEXT, nw1.rowcnt 
) 
SELECT * 
FROM phrasedwords pw 
    INNER JOIN test_PHRASE tp 
    ON LEFT(pw.PHRASE_TEXT, LEN(tp.PHRASE_TEXT)) = tp.PHRASE_TEXT 
ORDER BY pw.OBJ_FK, pw.WORD_ID 
+0

आधा कोड, सेट-आधारित और 10X तेज। सरल। और वे (@ जो) ने कहा कि यह नहीं किया जाना चाहिए/नहीं किया जाना चाहिए। – Laramie

+0

@ लारामी: धन्यवाद, आप बहुत दयालु हैं। आप जानते हैं, जब जो ने कहा कि यह कुछ ऐसा नहीं दिखता है जो आपको एसक्यूएल में करना चाहिए, शायद वह वास्तव में मतलब था कि उसे यह पसंद नहीं आया कि उसे कैसा लगा? लेकिन इसमें कोई आश्चर्य की बात नहीं है, आप भी नहीं थे! :) –

+0

@Andiry: "ऐसा लगता है कि ऐसा नहीं किया जाना चाहिए" टिप्पणी के बारे में लिया गया है, लेकिन मुझे यह मानना ​​है कि जब मैं कुछ प्रश्नोत्तरी से अधिक अपने प्रश्न को बर्खास्त कर रहा था, तो मुझे यह स्वीकार करना पड़ा कि मैं इसे प्राप्त किए गए अपवर्तकों पर थोड़ा उलझन में था। क्या उन्होंने रिक ओकेस्क को बताया कि वह पॉलिना पोरिज़कोवा से शादी नहीं कर सका? आपने दिखाया है कि इसे इच्छित टूल का प्रभावी ढंग से उपयोग किया जा सकता है। – Laramie

0

Split फ़ंक्शन का उपयोग करना चाहिए।

स्प्लिट समारोह

CREATE FUNCTION dbo.Split 
(
    @RowData nvarchar(2000), 
    @SplitOn nvarchar(5) 
) 
RETURNS @RtnValue table 
(
    Id int identity(1,1), 
    Data nvarchar(100) 
) 
AS 
BEGIN 
    Declare @Cnt int 
    Set @Cnt = 1 

    While (Charindex(@SplitOn,@RowData)>0) 
    Begin 
     Insert Into @RtnValue (data) 
     Select 
      Data = ltrim(rtrim(Substring(@RowData,1,Charindex(@SplitOn,@RowData)-1))) 

     Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData)) 
     Set @Cnt = @Cnt + 1 
    End 

    Insert Into @RtnValue (data) 
    Select Data = ltrim(rtrim(@RowData)) 

    Return 
END 

एसक्यूएल वक्तव्य

SELECT DISTINCT p.* 
FROM dbo.test_PHRASE p 
     LEFT OUTER JOIN (
      SELECT p.ID 
      FROM dbo.test_PHRASE p 
        CROSS APPLY dbo.Split(p.PHRASE_TEXT, ' ') sp 
        LEFT OUTER JOIN dbo.test_WORDS w ON w.WORD_TEXT = sp.Data 
      WHERE w.OBJ_FK IS NULL 
     ) ignore ON ignore.ID = p.ID 
WHERE ignore.ID IS NULL   
+1

वाक्यांशों को विभाजित करने के लिए यह एक ठोस दृष्टिकोण है, लेकिन यह आवश्यकता को पूरा नहीं करता है। यह test_PHRASE से किसी भी रिकॉर्ड से मेल खाता है जिसका शब्द test_Word में मौजूद है, जो कि उदाहरण में हर वाक्यांश है। लक्ष्य test_Word से पहले शब्द से मेल खाना है यदि यह test_phrase के वाक्यांशों में से एक का पहला शब्द है। परीक्षण मामले से डेटा का उपयोग करते हुए, 'xxx xxx xxx' और 'xxx www ppp' को test_word में किसी शब्द से मेल नहीं करना चाहिए क्योंकि [xxx] [xxx] [xxx] और [xxx] [www] [ppp] प्रकट नहीं होता test_Word में contiguously। क्षमा करें अगर मैं स्पष्ट नहीं था। आवश्यकता की जटिलता को संवाद करना चुनौतीपूर्ण है। – Laramie

+0

@ लारामी, फॉलो अप के लिए thx। अपने प्रश्न को दोबारा पढ़ना, यह काफी स्पष्ट था। त्रुटि मेरी तरफ थी। –

0

यह अन्य समाधानों की तुलना में थोड़ा बेहतर प्रदर्शन करता है। अगर आपको WORD_ID की आवश्यकता नहीं है, तो बस WORD_TEXT, आप एक संपूर्ण कॉलम निकाल सकते हैं।मुझे पता है कि यह एक साल पहले था, लेकिन मुझे आश्चर्य है कि क्या आप 3 सेकंड नीचे 30 एमएस तक प्राप्त कर सकते हैं? :)

यदि यह क्वेरी अच्छी लगती है, तो मेरी सबसे बड़ी गति सलाह पूरे वाक्यांशों को एक अलग तालिका में रखना है (आपके उदाहरण डेटा का उपयोग करके, इसमें लंबाई 8 शब्दों और 4 शब्दों के वाक्यांशों के साथ केवल 2 पंक्तियां होंगी)।

SELECT 
    W.OBJ_FK, 
    X.Phrase, 
    P.*, 
    Left(P.PHRASE_TEXT, 
     IsNull(NullIf(CharIndex(' ', P.PHRASE_TEXT), 0) - 1, 2147483647) 
    ) WORD_TEXT, 
    Len(Left(X.Phrase, PatIndex('%' + P.PHRASE_TEXT + '%', ' ' + X.Phrase) - 1)) 
     - Len(Replace(
     Left(X.Phrase, PatIndex('%' + P.PHRASE_TEXT + '%', X.Phrase) - 1), ' ', '') 
    ) 
     WORD_ID 
FROM 
    (SELECT DISTINCT OBJ_FK FROM dbo.test_WORDS) W 
    CROSS APPLY (
     SELECT RTrim((SELECT WORD_TEXT + ' ' 
     FROM dbo.test_WORDS W2 
     WHERE W.OBJ_FK = W2.OBJ_FK 
     ORDER BY W2.WORD_ID 
     FOR XML PATH (''))) Phrase 
    ) X 
    INNER JOIN dbo.test_PHRASE P 
     ON X.Phrase LIKE '%' + P.PHRASE_TEXT + '%'; 

जिज्ञासा के लिए यहां एक और संस्करण है। यह काफी प्रदर्शन नहीं करता है।

WITH Calc AS (
    SELECT 
     P.ID, 
     P.PHRASE_TEXT, 
     W.OBJ_FK, 
     W.WORD_ID StartID, 
     W.WORD_TEXT StartText, 
     W.WORD_ID, 
     Len(W.WORD_TEXT) + 2 NextPos, 
     Convert(varchar(150), W.WORD_TEXT) MatchingPhrase 
    FROM 
     dbo.test_PHRASE P 
     INNER JOIN dbo.test_WORDS W 
     ON P.PHRASE_TEXT + ' ' LIKE W.WORD_TEXT + ' %' 
    UNION ALL 
    SELECT 
     C.ID, 
     C.PHRASE_TEXT, 
     C.OBJ_FK, 
     C.StartID, 
     C.StartText, 
     W.WORD_ID, 
     C.NextPos + Len(W.WORD_TEXT) + 1, 
     Convert(varchar(150), C.MatchingPhrase + Coalesce(' ' + W.WORD_TEXT, '')) 
    FROM 
     Calc C 
     INNER JOIN dbo.test_WORDS W 
     ON C.OBJ_FK = W.OBJ_FK 
     AND C.WORD_ID + 1 = W.WORD_ID 
     AND Substring(C.PHRASE_TEXT, C.NextPos, 2147483647) + ' ' LIKE W.WORD_TEXT + ' %' 
) 
SELECT C.OBJ_FK, C.PHRASE_TEXT, C.StartID, C.StartText, C.ID 
FROM Calc C 
WHERE C.PHRASE_TEXT = C.MatchingPhrase; 
संबंधित मुद्दे