प्रति समूह n पंक्तियों को पुनः प्राप्त करना


88

मुझे अक्सर एक परिणाम सेट में प्रत्येक समूह से कई पंक्तियों का चयन करने की आवश्यकता होती है।

उदाहरण के लिए, मैं प्रति ग्राहक 'एन' उच्चतम या निम्नतम हाल के ऑर्डर मूल्यों को सूचीबद्ध करना चाह सकता हूं।

अधिक जटिल मामलों में, सूची में पंक्तियों की संख्या प्रति समूह (समूहन / अभिभावक रिकॉर्ड की एक विशेषता द्वारा परिभाषित) भिन्न हो सकती है। यह हिस्सा निश्चित रूप से वैकल्पिक / अतिरिक्त क्रेडिट के लिए है और लोगों को जवाब देने से रोकने का इरादा नहीं है।

SQL Server 2005 और बाद में इस प्रकार की समस्याओं को हल करने के लिए मुख्य विकल्प क्या हैं? प्रत्येक विधि के मुख्य फायदे और नुकसान क्या हैं?

AdventureWorks उदाहरण (स्पष्टता के लिए, वैकल्पिक)

  1. TransactionHistoryप्रत्येक उत्पाद के लिए तालिका से पांच सबसे हाल के लेन-देन की तारीखों और आईडी को सूचीबद्ध करें , जो एम से आर समावेशी पत्र के साथ शुरू होता है।
  2. फिर से, लेकिन nप्रति उत्पाद इतिहास लाइनों के साथ , जहां उत्पाद विशेषता nका पांच गुना है DaysToManufacture
  3. समान, विशेष मामले के लिए जहां प्रति उत्पाद एक इतिहास लाइन की आवश्यकता होती है (एकल सबसे हाल की प्रविष्टि TransactionDate, टाई-ब्रेक ऑन TransactionID

जवाबों:


70

आइए मूल परिदृश्य से शुरू करते हैं।

अगर मुझे किसी तालिका से कुछ पंक्तियाँ मिलनी हैं, तो मेरे पास दो मुख्य विकल्प हैं: रैंकिंग कार्य; या TOP

पहले, आइए Production.TransactionHistoryकिसी विशेष के लिए पूरे सेट पर विचार करें ProductID:

SELECT h.TransactionID, h.ProductID, h.TransactionDate
FROM Production.TransactionHistory h
WHERE h.ProductID = 800;

यह 418 पंक्तियों को लौटाता है, और योजना यह दिखाती है कि यह तालिका की प्रत्येक पंक्ति की जांच कर रही है - एक अप्रतिबंधित क्लस्टर इंडेक्स स्कैन, फ़िल्टर प्रदान करने के लिए एक विधेय के साथ। 797 यहां पढ़ता है, जो बदसूरत है।

'अवशिष्ट' के साथ महँगा स्कैन

तो आइए इसे उचित बनाएं, और एक इंडेक्स बनाएं जो अधिक उपयोगी होगा। हमारी स्थितियों में एक समानता मैच के लिए कॉल ProductIDकिया जाता है, जिसके बाद सबसे हाल ही में खोज की जाती है TransactionDate। हम की जरूरत TransactionIDभी लौट आए, तो चलो साथ चलते हैं: CREATE INDEX ix_FindingMostRecent ON Production.TransactionHistory (ProductID, TransactionDate) INCLUDE (TransactionID);

ऐसा करने के बाद, हमारी योजना में काफी बदलाव आता है, और पढ़ता है और घटकर सिर्फ 3. हो जाता है। इसलिए हम पहले से ही 250x या इससे अधिक की चीजों में सुधार कर रहे हैं ...

बेहतर योजना

अब जब हमने खेल का मैदान समतल कर लिया है, तो शीर्ष विकल्प - रैंकिंग फ़ंक्शंस और देखें TOP

WITH Numbered AS
(
SELECT h.TransactionID, h.ProductID, h.TransactionDate, ROW_NUMBER() OVER (ORDER BY TransactionDate DESC) AS RowNum
FROM Production.TransactionHistory h
WHERE h.ProductID = 800
)
SELECT TransactionID, ProductID, TransactionDate
FROM Numbered
WHERE RowNum <= 5;

SELECT TOP (5) h.TransactionID, h.ProductID, h.TransactionDate
FROM Production.TransactionHistory h
WHERE h.ProductID = 800
ORDER BY TransactionDate DESC;

दो योजनाएँ - मूल TOP \ RowNum

आप देखेंगे कि दूसरा ( TOP) क्वेरी और प्लान दोनों में पहले की तुलना में बहुत सरल है। लेकिन बहुत महत्वपूर्ण रूप से, वे दोनों TOPवास्तव में सूचकांक से बाहर खींची जाने वाली पंक्तियों की संख्या को सीमित करने के लिए उपयोग करते हैं। लागत केवल अनुमान लगाने और अनदेखा करने के लायक है, लेकिन आप दो योजनाओं में बहुत समानता देख सकते हैं, ROW_NUMBER()संस्करण के साथ संख्याओं को असाइन करने और तदनुसार फ़िल्टर करने के लिए अतिरिक्त काम करते हैं, और दोनों प्रश्न समाप्त होते हैं बस 2 करने के लिए पढ़ता है उनका काम। क्वेरी ऑप्टिमाइज़र निश्चित रूप से एक ROW_NUMBER()फ़ील्ड पर फ़िल्टर करने के विचार को पहचानता है , यह महसूस करता है कि यह शीर्ष ऑपरेटर का उपयोग उन पंक्तियों को अनदेखा करने के लिए कर सकता है जिनकी आवश्यकता नहीं है। ये दोनों प्रश्न काफी अच्छे हैं - TOPयह इतना बेहतर नहीं है कि यह कोड बदलने के लायक है, लेकिन शुरुआती लोगों के लिए यह सरल और संभवतः स्पष्ट है।

तो यह एक ही उत्पाद में काम करता है। लेकिन हमें यह विचार करने की आवश्यकता है कि यदि हमें कई उत्पादों में ऐसा करने की आवश्यकता है तो क्या होगा।

पुनरावृत्त प्रोग्रामर रुचि के उत्पादों के माध्यम से लूपिंग के विचार पर विचार करने जा रहा है, और इस क्वेरी को कई बार कॉल कर रहा है, और हम वास्तव में इस रूप में एक क्वेरी लिखने के साथ दूर हो सकते हैं - कर्सर का उपयोग नहीं कर रहे हैं, लेकिन उपयोग कर रहे हैं APPLY। मैं उपयोग कर रहा हूं OUTER APPLY, यह समझकर कि हम उत्पाद को NULL के साथ वापस करना चाहते हैं, यदि इसके लिए कोई लेन-देन नहीं है।

SELECT p.Name, p.ProductID, t.TransactionID, t.TransactionDate
FROM 
Production.Product p
OUTER APPLY (
    SELECT TOP (5) h.TransactionID, h.ProductID, h.TransactionDate
    FROM Production.TransactionHistory h
    WHERE h.ProductID = p.ProductID
    ORDER BY TransactionDate DESC
) t
WHERE p.Name >= 'M' AND p.Name < 'S';

इसके लिए योजना प्रत्येक प्रोग्राम के लिए पुनरावृत्त प्रोग्रामर्स विधि - नेस्टेड लूप, एक शीर्ष ऑपरेशन और सीक (उन 2 पढ़ता है जो हमारे पास पहले था) कर रही है। यह उत्पाद के खिलाफ 4 रीड्स, और TransactionHistory के खिलाफ 360 देता है।

APPLY योजना

उपयोग करना ROW_NUMBER(), विधि का उपयोग क्लॉज PARTITION BYमें करना OVERहै, ताकि हम प्रत्येक उत्पाद के लिए नंबरिंग को पुनः आरंभ करें। यह फिर पहले की तरह फ़िल्टर किया जा सकता है। यह योजना काफी अलग है। तार्किक रीड्स TransactionHistory पर लगभग 15% कम हैं, एक पूर्ण सूचकांक स्कैन के साथ पंक्तियों को बाहर निकालने के लिए चल रहा है।

WITH Numbered AS
(
SELECT p.Name, p.ProductID, h.TransactionID, h.TransactionDate, ROW_NUMBER() OVER (PARTITION BY h.ProductID ORDER BY h.TransactionDate DESC) AS RowNum
FROM Production.Product p
LEFT JOIN Production.TransactionHistory h ON h.ProductID = p.ProductID
WHERE p.Name >= 'M' AND p.Name < 'S'
)
SELECT Name, ProductID, TransactionID, TransactionDate
FROM Numbered n
WHERE RowNum <= 5;

ROW_NUMBER योजना

गौरतलब है कि हालांकि, इस योजना में एक महंगा सॉर्ट ऑपरेटर है। Merge Join से TransactionHistory में पंक्तियों के क्रम को बनाए रखने के लिए प्रतीत नहीं होता है, रोवर को खोजने में सक्षम होने के लिए डेटा का सहारा लेना चाहिए। यह बहुत कम पढ़ता है, लेकिन यह ब्लॉकिंग सॉर्ट दर्दनाक महसूस कर सकता है। APPLYनेस्टेड लूप का उपयोग करते हुए , बस कुछ पंक्तियों के बाद पहली पंक्तियों को बहुत जल्दी वापस कर दिया जाएगा, लेकिन एक छंटनी के साथ, ROW_NUMBER()अधिकांश कार्य समाप्त होने के बाद केवल पंक्तियाँ वापस आ जाएंगी।

दिलचस्प बात यह है कि अगर इसके बजाय ROW_NUMBER()क्वेरी का उपयोग किया INNER JOINजाता है LEFT JOIN, तो एक अलग योजना सामने आती है।

ROW_NUMBER () ININ JOIN के साथ

इस योजना में एक नेस्टेड लूप का उपयोग किया जाता है, जैसे कि APPLY। लेकिन कोई शीर्ष ऑपरेटर नहीं है, इसलिए यह प्रत्येक उत्पाद के लिए सभी लेन-देन को खींचता है, और पहले की तुलना में बहुत अधिक रीड्स का उपयोग करता है - 492 TransactionHistory के खिलाफ पढ़ता है। यहाँ मर्ज ज्वाइन का विकल्प न चुनने का कोई अच्छा कारण नहीं है, इसलिए मुझे लगता है कि इस योजना को 'गुड इनफ' माना गया था। फिर भी - यह ब्लॉक नहीं करता है, जो अच्छा है - बस उतना अच्छा नहीं है APPLY

PARTITION BYमैंने जिस कॉलम का उपयोग किया ROW_NUMBER()था , वह h.ProductIDदोनों ही मामलों में था , क्योंकि मैं QO को प्रोडक्ट टेबल में शामिल होने से पहले RowNum वैल्यू के उत्पादन का विकल्प देना चाहता था। यदि मैं उपयोग करता हूं p.ProductID, तो हम उसी आकार की योजना को देखते हैं, जिसमें INNER JOINभिन्नता है।

WITH Numbered AS
(
SELECT p.Name, p.ProductID, h.TransactionID, h.TransactionDate, ROW_NUMBER() OVER (PARTITION BY p.ProductID ORDER BY h.TransactionDate DESC) AS RowNum
FROM Production.Product p
LEFT JOIN Production.TransactionHistory h ON h.ProductID = p.ProductID
WHERE p.Name >= 'M' AND p.Name < 'S'
)
SELECT Name, ProductID, TransactionID, TransactionDate
FROM Numbered n
WHERE RowNum <= 5;

लेकिन जॉइन करने वाले ऑपरेटर 'इनर जॉइन' की जगह 'लेफ्ट आउटर जॉइन' कहते हैं। रीड्स की संख्या अभी भी 500 के नीचे है TransactionHistory टेबल के खिलाफ पढ़ता है।

H.ProductID के बजाय p.ProductID पर विभाजन

वैसे भी - वापस सवाल पर वापस ...

हमने प्रश्न 1 का उत्तर दिया है , जिसमें दो विकल्प हैं जिन्हें आप चुन सकते हैं और चुन सकते हैं। व्यक्तिगत रूप से, मुझे APPLYविकल्प पसंद है ।

एक चर संख्या ( प्रश्न 2 ) का उपयोग करने के लिए इसका विस्तार करने के लिए , 5बस तदनुसार बदलने की आवश्यकता है। ओह, और मैंने एक और इंडेक्स जोड़ा है, ताकि उस पर एक इंडेक्स Production.Product.Nameहो जिसमें DaysToManufactureकॉलम शामिल हो ।

WITH Numbered AS
(
SELECT p.Name, p.ProductID, p.DaysToManufacture, h.TransactionID, h.TransactionDate, ROW_NUMBER() OVER (PARTITION BY h.ProductID ORDER BY h.TransactionDate DESC) AS RowNum
FROM Production.Product p
LEFT JOIN Production.TransactionHistory h ON h.ProductID = p.ProductID
WHERE p.Name >= 'M' AND p.Name < 'S'
)
SELECT Name, ProductID, TransactionID, TransactionDate
FROM Numbered n
WHERE RowNum <= 5 * DaysToManufacture;

SELECT p.Name, p.ProductID, t.TransactionID, t.TransactionDate
FROM 
Production.Product p
OUTER APPLY (
    SELECT TOP (5 * p.DaysToManufacture) h.TransactionID, h.ProductID, h.TransactionDate
    FROM Production.TransactionHistory h
    WHERE h.ProductID = p.ProductID
    ORDER BY TransactionDate DESC
) t
WHERE p.Name >= 'M' AND p.Name < 'S';

और दोनों योजनाएं लगभग वही हैं जो वे पहले थे!

परिवर्तनशील पंक्तियाँ

फिर, अनुमानित लागतों को अनदेखा करें - लेकिन मुझे अभी भी TOP परिदृश्य पसंद है, क्योंकि यह बहुत अधिक सरल है, और योजना में कोई अवरोधक ऑपरेटर नहीं है। Zeroes की संख्या अधिक होने के कारण TransactionHistory पर रीड कम हैं DaysToManufacture, लेकिन वास्तविक जीवन में, मुझे संदेह है कि हम उस कॉलम को चुन रहे होंगे। ;)

ब्लॉक से बचने का एक तरीका एक योजना के साथ आना ROW_NUMBER()है जो जुड़ने के दाईं ओर (योजना में) को थोड़ा संभालती है । हम सीटीई के बाहर जॉइन करके ऐसा होने के लिए राजी कर सकते हैं।

WITH Numbered AS
(
SELECT h.TransactionID, h.ProductID, h.TransactionDate, ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY TransactionDate DESC) AS RowNum
FROM Production.TransactionHistory h
)
SELECT p.Name, p.ProductID, t.TransactionID, t.TransactionDate
FROM Production.Product p
LEFT JOIN Numbered t ON t.ProductID = p.ProductID
    AND t.RowNum <= 5 * p.DaysToManufacture
WHERE p.Name >= 'M' AND p.Name < 'S';

यहां योजना सरल दिखती है - यह अवरुद्ध नहीं है, लेकिन एक छिपा हुआ खतरा है।

सीटीई के बाहर शामिल होना

कंप्यूट स्केलर पर ध्यान दें जो उत्पाद तालिका से डेटा खींच रहा है। यह 5 * p.DaysToManufactureमान बाहर काम कर रहा है । यह मान उस शाखा में पारित नहीं किया जा रहा है जो TransactionHistory तालिका से डेटा खींच रही है, इसका उपयोग मर्ज जॉइन में किया जा रहा है। अवशिष्ट के रूप में।

डरपोक अवशिष्ट!

तो मर्ज ज्वाइन सभी पंक्तियों का उपभोग कर रहा है, न केवल पहले हालांकि-बहुत-से-आवश्यक हैं, लेकिन उनमें से सभी और फिर एक अवशिष्ट जांच कर रहे हैं। यह खतरनाक है क्योंकि लेनदेन की संख्या बढ़ जाती है। मैं इस परिदृश्य का प्रशंसक नहीं हूं - मर्ज जॉइन में अवशिष्ट की भविष्यवाणी जल्दी से बढ़ सकती है। एक और कारण है कि मैं APPLY/TOPपरिदृश्य को क्यों पसंद करता हूं ।

विशेष मामले में जहां यह प्रश्न 3 के लिए बिल्कुल एक पंक्ति है , हम स्पष्ट रूप से उसी प्रश्नों का उपयोग कर सकते हैं, लेकिन 1इसके बजाय 5। लेकिन फिर हमारे पास एक अतिरिक्त विकल्प है, जो नियमित समुच्चय का उपयोग करना है।

SELECT ProductID, MAX(TransactionDate)
FROM Production.TransactionHistory
GROUP BY ProductID;

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

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

मैंने यहां समानता की खोज करने की कोशिश नहीं की है, या प्रश्न 3 में बहुत कठिनता से डुबकी लगाई है, जिसे मैं एक विशेष मामले के रूप में देखता हूं जो लोग शायद ही कभी सहमति और विभाजन की जटिलता के आधार पर चाहते हैं। यहां पर मुख्य बात यह है कि ये दोनों विकल्प बहुत मजबूत हैं।

मैं पसंद करता हूं APPLY। यह स्पष्ट है, यह शीर्ष ऑपरेटर का अच्छी तरह से उपयोग करता है, और यह शायद ही कभी अवरोध का कारण बनता है।


44

SQL सर्वर 2005 और ऊपर में ऐसा करने का विशिष्ट तरीका CTE और विंडोिंग फ़ंक्शन का उपयोग करना है। शीर्ष प्रति समूह के लिए आप बस ROW_NUMBER()एक PARTITIONखंड के साथ उपयोग कर सकते हैं , और बाहरी क्वेरी में उस के खिलाफ फ़िल्टर कर सकते हैं । इसलिए, उदाहरण के लिए, प्रति ग्राहक शीर्ष 5 सबसे हाल के आदेश इस तरह प्रदर्शित किए जा सकते हैं:

DECLARE @top INT;
SET @top = 5;

;WITH grp AS 
(
   SELECT CustomerID, OrderID, OrderDate,
     rn = ROW_NUMBER() OVER
     (PARTITION BY CustomerID ORDER BY OrderDate DESC)
   FROM dbo.Orders
)
SELECT CustomerID, OrderID, OrderDate
  FROM grp
  WHERE rn <= @top
  ORDER BY CustomerID, OrderDate DESC;

आप इसके साथ भी कर सकते हैं CROSS APPLY:

DECLARE @top INT;
SET @top = 5;

SELECT c.CustomerID, o.OrderID, o.OrderDate
FROM dbo.Customers AS c
CROSS APPLY 
(
    SELECT TOP (@top) OrderID, OrderDate 
    FROM dbo.Orders AS o
    WHERE CustomerID = c.CustomerID
    ORDER BY OrderDate DESC
) AS o
ORDER BY c.CustomerID, o.OrderDate DESC;

निर्दिष्ट अतिरिक्त विकल्प के साथ, पॉल का कहना है कि ग्राहक तालिका में यह दर्शाया गया है कि प्रति ग्राहक में कितनी पंक्तियाँ शामिल हैं:

;WITH grp AS 
(
   SELECT CustomerID, OrderID, OrderDate,
     rn = ROW_NUMBER() OVER
     (PARTITION BY CustomerID ORDER BY OrderDate DESC)
   FROM dbo.Orders
)
SELECT c.CustomerID, grp.OrderID, grp.OrderDate
  FROM grp 
  INNER JOIN dbo.Customers AS c
  ON grp.CustomerID = c.CustomerID
  AND grp.rn <= c.Number_of_Recent_Orders_to_Show
  ORDER BY c.CustomerID, grp.OrderDate DESC;

और फिर से, CROSS APPLYजोड़े गए विकल्प का उपयोग और शामिल करना कि ग्राहकों की तालिका में कुछ कॉलम द्वारा पंक्तियों की संख्या निर्धारित की जाए:

SELECT c.CustomerID, o.OrderID, o.OrderDate
FROM dbo.Customers AS c
CROSS APPLY 
(
    SELECT TOP (c.Number_of_Recent_Orders_to_Show) OrderID, OrderDate 
    FROM dbo.Orders AS o
    WHERE CustomerID = c.CustomerID
    ORDER BY OrderDate DESC
) AS o
ORDER BY c.CustomerID, o.OrderDate DESC;

ध्यान दें कि ये डेटा वितरण और सहायक इंडेक्स की उपलब्धता के आधार पर अलग-अलग प्रदर्शन करेंगे, इसलिए प्रदर्शन का अनुकूलन करना और सबसे अच्छी योजना प्राप्त करना वास्तव में स्थानीय कारकों पर निर्भर करेगा।

व्यक्तिगत रूप से, मैं सीटीई और विंडोिंग समाधानों को अधिक पसंद करता हूं CROSS APPLY/ TOPक्योंकि वे तर्क को बेहतर ढंग से अलग करते हैं और अधिक सहज (मेरे लिए) हैं। सामान्य तौर पर (इस मामले में और मेरे सामान्य अनुभव दोनों में), सीटीई दृष्टिकोण अधिक कुशल योजनाएं (नीचे दिए गए उदाहरण) का निर्माण करता है, लेकिन इसे एक सार्वभौमिक सत्य के रूप में नहीं लिया जाना चाहिए - आपको हमेशा अपने परिदृश्यों का परीक्षण करना चाहिए, खासकर अगर सूचकांक बदल गए हैं या डेटा में काफी कमी आई है।


एडवेंचरवर्क्स उदाहरण - बिना किसी बदलाव के

  1. TransactionHistoryप्रत्येक उत्पाद के लिए तालिका से पांच सबसे हाल के लेन-देन की तारीखों और आईडी को सूचीबद्ध करें , जो एम से आर समावेशी पत्र के साथ शुरू होता है।
-- CTE / OVER()

;WITH History AS
(
  SELECT p.ProductID, p.Name, t.TransactionID, t.TransactionDate,
    rn = ROW_NUMBER() OVER 
    (PARTITION BY t.ProductID ORDER BY t.TransactionDate DESC)
  FROM Production.Product AS p
  INNER JOIN Production.TransactionHistory AS t
  ON p.ProductID = t.ProductID
  WHERE p.Name >= N'M' AND p.Name < N'S'
)
SELECT ProductID, Name, TransactionID, TransactionDate
FROM History 
WHERE rn <= 5;

-- CROSS APPLY

SELECT p.ProductID, p.Name, t.TransactionID, t.TransactionDate
FROM Production.Product AS p
CROSS APPLY
(
  SELECT TOP (5) TransactionID, TransactionDate
  FROM Production.TransactionHistory
  WHERE ProductID = p.ProductID
  ORDER BY TransactionDate DESC
) AS t
WHERE p.Name >= N'M' AND p.Name < N'S';

रनटाइम मीट्रिक में इन दोनों की तुलना:

यहाँ छवि विवरण दर्ज करें

CTE / OVER()योजना:

यहाँ छवि विवरण दर्ज करें

CROSS APPLY योजना:

यहाँ छवि विवरण दर्ज करें

सीटीई योजना अधिक जटिल लगती है, लेकिन यह वास्तव में बहुत अधिक कुशल है। अनुमानित लागत% संख्या पर थोड़ा ध्यान दें, लेकिन अधिक महत्वपूर्ण वास्तविक टिप्पणियों पर ध्यान केंद्रित करें , जैसे कि बहुत कम पढ़ी जाती हैं और बहुत कम अवधि। मैंने भी बिना समानता के इन्हें चलाया, और यह अंतर नहीं था। रनटाइम मैट्रिक्स और सीटीई योजना ( CROSS APPLYयोजना एक ही रही):

यहाँ छवि विवरण दर्ज करें

यहाँ छवि विवरण दर्ज करें

  1. फिर से, लेकिन nप्रति उत्पाद इतिहास लाइनों के साथ , जहां उत्पाद विशेषता nका पांच गुना है DaysToManufacture

यहां बहुत छोटे बदलाव आवश्यक हैं। CTE के लिए, हम आंतरिक क्वेरी में एक कॉलम जोड़ सकते हैं, और बाहरी क्वेरी पर फ़िल्टर कर सकते हैं; इसके लिए CROSS APPLY, हम सहसंबद्ध के अंदर गणना कर सकते हैं TOP। आपको लगता है कि यह CROSS APPLYसमाधान के लिए कुछ दक्षता प्रदान करेगा , लेकिन इस मामले में ऐसा नहीं होता है। क्वेरी:

-- CTE / OVER()

;WITH History AS
(
  SELECT p.ProductID, p.Name, p.DaysToManufacture, t.TransactionID, t.TransactionDate,
    rn = ROW_NUMBER() OVER 
    (PARTITION BY t.ProductID ORDER BY t.TransactionDate DESC)
  FROM Production.Product AS p
  INNER JOIN Production.TransactionHistory AS t
  ON p.ProductID = t.ProductID
  WHERE p.Name >= N'M' AND p.Name < N'S'
)
SELECT ProductID, Name, TransactionID, TransactionDate
FROM History 
WHERE rn <= (5 * DaysToManufacture);

-- CROSS APPLY

SELECT p.ProductID, p.Name, t.TransactionID, t.TransactionDate
FROM Production.Product AS p
CROSS APPLY
(
  SELECT TOP (5 * p.DaysToManufacture) TransactionID, TransactionDate
  FROM Production.TransactionHistory
  WHERE ProductID = p.ProductID
  ORDER BY TransactionDate DESC
) AS t
WHERE p.Name >= N'M' AND p.Name < N'S';

रनटाइम परिणाम:

यहाँ छवि विवरण दर्ज करें

समानांतर सीटीई / OVER()योजना:

यहाँ छवि विवरण दर्ज करें

एकल थ्रेडेड CTE / OVER()योजना:

यहाँ छवि विवरण दर्ज करें

CROSS APPLY योजना:

यहाँ छवि विवरण दर्ज करें

  1. समान, विशेष मामले के लिए जहां प्रति उत्पाद एक इतिहास लाइन की आवश्यकता होती है (एकल सबसे हाल की प्रविष्टि TransactionDate, टाई-ब्रेक ऑन TransactionID

फिर से, यहाँ छोटे परिवर्तन। CTE समाधान में, हम जोड़ने TransactionIDके लिए OVER()खंड, और करने के लिए बाहरी फिल्टर बदलने के rn = 1। हमारे लिए CROSS APPLY, हम TOPकरने के लिए बदल जाते हैं TOP (1), और TransactionIDआंतरिक में जोड़ें ORDER BY

-- CTE / OVER()

;WITH History AS
(
  SELECT p.ProductID, p.Name, t.TransactionID, t.TransactionDate,
    rn = ROW_NUMBER() OVER 
    (PARTITION BY t.ProductID ORDER BY t.TransactionDate DESC, TransactionID DESC)
  FROM Production.Product AS p
  INNER JOIN Production.TransactionHistory AS t
  ON p.ProductID = t.ProductID
  WHERE p.Name >= N'M' AND p.Name < N'S'
)
SELECT ProductID, Name, TransactionID, TransactionDate
FROM History 
WHERE rn = 1;

-- CROSS APPLY

SELECT p.ProductID, p.Name, t.TransactionID, t.TransactionDate
FROM Production.Product AS p
CROSS APPLY
(
  SELECT TOP (1) TransactionID, TransactionDate
  FROM Production.TransactionHistory
  WHERE ProductID = p.ProductID
  ORDER BY TransactionDate DESC, TransactionID DESC
) AS t
WHERE p.Name >= N'M' AND p.Name < N'S';

रनटाइम परिणाम:

यहाँ छवि विवरण दर्ज करें

समानांतर सीटीई / OVER()योजना:

यहाँ छवि विवरण दर्ज करें

एकल थ्रेडेड CTE / OVER () योजना:

यहाँ छवि विवरण दर्ज करें

CROSS APPLY योजना:

यहाँ छवि विवरण दर्ज करें

विंडिंग फ़ंक्शंस हमेशा सबसे अच्छा विकल्प नहीं होते हैं (एक बार में चलते हैं COUNT(*) OVER()), और ये समूह समस्या के अनुसार प्रति पंक्तियों को हल करने के लिए केवल दो दृष्टिकोण नहीं हैं, लेकिन इस विशिष्ट मामले में - स्कीमा, मौजूदा अनुक्रमित और डेटा वितरण - CTE ने सभी सार्थक खातों का बेहतर प्रदर्शन किया।


एडवेंचरवर्क्स उदाहरण - लचीलेपन के साथ अनुक्रमणिका को जोड़ने के लिए

हालाँकि, यदि आप एक समर्थन सूचकांक जोड़ते हैं, तो एक पॉल के समान जो एक टिप्पणी में उल्लिखित है लेकिन 2 और 3 कॉलम के साथ क्रमबद्ध है DESC:

CREATE UNIQUE NONCLUSTERED INDEX UQ3 ON Production.TransactionHistory 
  (ProductID, TransactionDate DESC, TransactionID DESC);

आपको वास्तव में चारों ओर बहुत अधिक अनुकूल योजनाएं मिलेंगी, और CROSS APPLYतीनों मामलों में मेट्रिक्स दृष्टिकोण का पक्ष लेंगे।

यहाँ छवि विवरण दर्ज करें

यदि यह मेरा उत्पादन वातावरण होता, तो मैं शायद इस मामले में अवधि से संतुष्ट होता, और आगे अनुकूलन करने की जहमत नहीं उठाता।


यह SQL Server 2000 में बहुत अधिक बदसूरत था, जो समर्थन APPLYया OVER()खंड नहीं था ।


24

DBMS में, MySQL की तरह, जिसमें विंडो फ़ंक्शंस नहीं हैं या CROSS APPLY, ऐसा करने का तरीका मानक SQL (89) का उपयोग करना होगा। धीमा तरीका कुल मिलाकर एक त्रिकोणीय क्रॉस होगा। तेज रास्ता (लेकिन अभी भी है और शायद के रूप में पार लागू करें या ROW_NUMBER फ़ंक्शन का उपयोग कर के रूप में कुशल नहीं) होगा कि मैं क्या कॉल "गरीब आदमी का CROSS APPLY" । इस क्वेरी की दूसरों के साथ तुलना करना दिलचस्प होगा:

धारणा: Orders (CustomerID, OrderDate)एक UNIQUEबाधा है:

DECLARE @top INT;
SET @top = 5;

SELECT o.CustomerID, o.OrderID, o.OrderDate
  FROM dbo.Customers AS c
    JOIN dbo.Orders AS o
      ON  o.CustomerID = c.CustomerID
      AND o.OrderID IN
          ( SELECT TOP (@top) oi.OrderID
            FROM dbo.Orders AS oi
            WHERE oi.CustomerID = c.CustomerID
            ORDER BY oi.OrderDate DESC
          )
  ORDER BY CustomerID, OrderDate DESC ;

समूह में अनुकूलित शीर्ष पंक्तियों की अतिरिक्त समस्या के लिए:

SELECT o.CustomerID, o.OrderID, o.OrderDate
  FROM dbo.Customers AS c
    JOIN dbo.Orders AS o
      ON  o.CustomerID = c.CustomerID
      AND o.OrderID IN
          ( SELECT TOP (c.Number_of_Recent_Orders_to_Show) oi.OrderID
            FROM dbo.Orders AS oi
            WHERE oi.CustomerID = c.CustomerID
            ORDER BY oi.OrderDate DESC
          )
  ORDER BY CustomerID, OrderDate DESC ;

नोट: MySQL में, AND o.OrderID IN (SELECT TOP(@top) oi.OrderID ...)एक के बजाय उपयोग होगा AND o.OrderDate >= (SELECT oi.OrderDate ... LIMIT 1 OFFSET (@top - 1))। SQL- सर्वर ने FETCH / OFFSET2012 संस्करण में सिंटैक्स जोड़ा । यहां के प्रश्नों को IN (TOP...)पहले के संस्करणों के साथ काम करने के लिए समायोजित किया गया था।


21

मैंने थोड़ा अलग दृष्टिकोण लिया, मुख्य रूप से यह देखने के लिए कि यह तकनीक दूसरों की तुलना कैसे करेगी, क्योंकि विकल्प अच्छा है, है ना?

परीक्षण

क्यों नहीं हम केवल यह देखते हुए शुरू करते हैं कि विभिन्न तरीकों को एक दूसरे के खिलाफ कैसे खड़ा किया गया है। मैंने परीक्षण के तीन सेट किए:

  1. पहला सेट बिना डीबी संशोधनों के साथ चला
  2. इंडेक्स- TransactionDateआधारित प्रश्नों के समर्थन के लिए एक इंडेक्स बनाए जाने के बाद दूसरा सेट चला Production.TransactionHistory
  3. तीसरे सेट ने थोड़ी अलग धारणा बनाई। चूंकि सभी तीन परीक्षण उत्पादों की एक ही सूची के खिलाफ चले थे, अगर हम उस सूची को कैश कर दें तो क्या होगा? मेरी विधि एक इन-मेमोरी कैश का उपयोग करती है जबकि अन्य विधियां एक समान अस्थायी तालिका का उपयोग करती हैं। परीक्षणों के इस सेट के लिए परीक्षण के दूसरे सेट के लिए बनाया गया समर्थन सूचकांक अभी भी मौजूद है।

अतिरिक्त परीक्षण विवरण:

  • परीक्षण AdventureWorks2012SQL सर्वर 2012, SP2 (डेवलपर संस्करण) के विरुद्ध चलाए गए थे ।
  • प्रत्येक परीक्षण के लिए मैंने जिनके उत्तर को लेबल किया था, उनसे मैंने प्रश्न लिया था और यह किस विशेष प्रश्न से था।
  • मैंने क्वेरी विकल्प के "निष्पादन के बाद परिणाम त्यागें" विकल्प का उपयोग किया परिणाम।
  • कृपया ध्यान दें कि परीक्षणों के पहले दो सेटों के RowCountsलिए, मेरी विधि के लिए "बंद" दिखाई दें। यह मेरा तरीका है कि जो CROSS APPLYकुछ भी कर रहा है उसका एक मैनुअल कार्यान्वयन होने के कारण : यह प्रारंभिक क्वेरी को चलाता है Production.Productऔर 161 पंक्तियों को वापस प्राप्त करता है, जो तब इसके खिलाफ प्रश्नों के लिए उपयोग करता है Production.TransactionHistory। इसलिए, RowCountमेरी प्रविष्टियों के लिए मूल्य हमेशा अन्य प्रविष्टियों की तुलना में 161 अधिक हैं। परीक्षणों के तीसरे सेट में (कैशिंग के साथ) पंक्ति गणना सभी विधियों के लिए समान है।
  • मैंने निष्पादन योजनाओं पर भरोसा करने के बजाय आँकड़ों को पकड़ने के लिए SQL सर्वर प्रोफाइलर का उपयोग किया। हारून और मिकेल ने पहले से ही अपने प्रश्नों के लिए योजनाओं को दिखाते हुए एक अच्छा काम किया और उस जानकारी को पुन: पेश करने की आवश्यकता नहीं है। और मेरी पद्धति का उद्देश्य प्रश्नों को इतने सरल रूप में कम करना है कि यह वास्तव में मायने नहीं रखेगा। Profiler उपयोग करने का एक अतिरिक्त कारण है, लेकिन बाद में इसका उल्लेख किया जाएगा।
  • Name >= N'M' AND Name < N'S'निर्माण का उपयोग करने के बजाय , मैंने उपयोग करना चुना Name LIKE N'[M-R]%', और SQL सर्वर उनके साथ एक जैसा व्यवहार करता है।

परिणाम

कोई सहायक सूचकांक नहीं

यह अनिवार्य रूप से आउट-ऑफ-द-बॉक्स AdventureWorks2012 है। सभी मामलों में मेरी विधि स्पष्ट रूप से कुछ अन्य की तुलना में बेहतर है, लेकिन शीर्ष 1 या 2 विधियों के रूप में कभी भी अच्छा नहीं है।

टेस्ट 1 परीक्षण 1 परिणाम-बिना सूचकांक के
आरोन का सीटीई स्पष्ट रूप से यहां विजेता है।

टेस्ट 2 परीक्षण 2 परिणाम-बिना सूचकांक के
आरोन का सीटीई (फिर से) और मिकेल का दूसरा apply row_number()तरीका एक करीबी दूसरा है।

टेस्ट 3 परीक्षण 3 परिणाम-बिना सूचकांक के
हारून का सीटीई (फिर) विजेता है।

निष्कर्ष
जब कोई सहायक सूचकांक नहीं होता है TransactionDate, तो मेरी विधि मानक करने से बेहतर है CROSS APPLY, लेकिन फिर भी, सीटीई विधि का उपयोग स्पष्ट रूप से जाने का तरीका है।

सहायक सूचकांक (कोई कैशिंग नहीं) के साथ

परीक्षणों के इस सेट के लिए मैंने TransactionHistory.TransactionDateउस क्षेत्र में सभी प्रकार के प्रश्नों के क्रम से स्पष्ट सूचकांक को जोड़ा । मैं कहता हूं कि "स्पष्ट" क्योंकि अधिकांश अन्य उत्तर भी इस बिंदु पर सहमत हैं। और चूंकि प्रश्न सभी सबसे हाल की तिथियां चाहते हैं , इसलिए TransactionDateफ़ील्ड को आदेश दिया जाना चाहिए DESC, इसलिए मैंने केवल CREATE INDEXमिकेल के जवाब के निचले हिस्से में बयान पकड़ा और एक स्पष्ट जोड़ा FILLFACTOR:

CREATE INDEX [IX_TransactionHistoryX]
    ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC)
    WITH (FILLFACTOR = 100);

एक बार जब यह सूचकांक लागू हो जाता है, तो परिणाम काफी बदल जाते हैं।

टेस्ट 1 परीक्षण 1 परिणाम-समर्थन सूचकांक के साथ
इस बार यह मेरी विधि है जो कम से कम लॉजिकल रीड्स के मामले में आगे आती है। CROSS APPLYविधि, पहले टेस्ट 1 के लिए सबसे खराब अभिनेता, अवधि पर जीतता है और यहां तक कि CTE विधि धड़कता तार्किक पढ़ता है पर।

टेस्ट 2 परीक्षण 2 परिणाम-समर्थन सूचकांक के साथ
इस बार यह मिकेल की पहली apply row_number()विधि है जो रीड्स को देखते हुए विजेता है, जबकि पहले यह सबसे खराब प्रदर्शन करने वालों में से एक था। और अब मेरी विधि एक बहुत करीबी दूसरे स्थान पर आती है जब रीड्स को देखते हैं। वास्तव में, सीटीई पद्धति के बाहर, बाकी सभी रीड्स के संदर्भ में काफी करीब हैं।

टेस्ट 3 परीक्षण 3 परिणाम-समर्थन सूचकांक के साथ
यहां सीटीई अभी भी विजेता है, लेकिन अब सूचकांक बनाने से पहले मौजूद कठोर अंतर की तुलना में अन्य तरीकों के बीच का अंतर मुश्किल से ध्यान देने योग्य है।

निष्कर्ष
मेरी विधि की प्रयोज्यता अब अधिक स्पष्ट है, हालांकि यह उचित अनुक्रमणिका नहीं होने के कारण कम लचीला है।

समर्थन सूचकांक और कैशिंग के साथ

परीक्षणों के इस सेट के लिए मैंने कैशिंग का उपयोग किया क्योंकि, ठीक है, क्यों नहीं? मेरा तरीका इन-मेमोरी कैशिंग का उपयोग करने की अनुमति देता है जो अन्य तरीकों तक नहीं पहुंच सकता है। इसलिए निष्पक्ष होने के लिए, मैंने निम्नलिखित अस्थायी तालिका बनाई जो Product.Productसभी तीन परीक्षणों में उन अन्य तरीकों में सभी संदर्भों के लिए उपयोग की गई थी । इस DaysToManufactureक्षेत्र का उपयोग केवल टेस्ट नंबर 2 में किया जाता है, लेकिन एक ही तालिका का उपयोग करने के लिए SQL स्क्रिप्ट के अनुरूप होना आसान था और इसे वहां होने में कोई चोट नहीं आई।

CREATE TABLE #Products
(
    ProductID INT NOT NULL PRIMARY KEY,
    Name NVARCHAR(50) NOT NULL,
    DaysToManufacture INT NOT NULL
);

INSERT INTO #Products (ProductID, Name, DaysToManufacture)
    SELECT  p.ProductID, p.Name, p.DaysToManufacture
    FROM    Production.Product p
    WHERE   p.Name >= N'M' AND p.Name < N'S'
    AND    EXISTS (
                    SELECT  *
                    FROM    Production.TransactionHistory th
                    WHERE   th.ProductID = p.ProductID
                );

ALTER TABLE #Products REBUILD WITH (FILLFACTOR = 100);

टेस्ट 1 टेस्ट 1 परिणाम-सपोर्टिंग इंडेक्स और कैशिंग के साथ
सभी तरीकों से कैशिंग से समान रूप से लाभ होता है, और मेरी विधि अभी भी आगे निकलती है।

टेस्ट 2 परीक्षण के परिणाम 2 - समर्थन सूचकांक और कैशिंग के साथ
यहां अब हम लाइनअप में अंतर देखते हैं क्योंकि मेरी विधि मुश्किल से आगे निकलती है, केवल 2 रीड्स मिकेल की पहली apply row_number()विधि से बेहतर है , जबकि कैशिंग के बिना मेरी विधि 4 रीड्स से पीछे थी।

परीक्षण 3 परीक्षण 3 परिणाम-समर्थन सूचकांक और कैशिंग के साथ
कृपया नीचे (लाइन के नीचे) की ओर अपडेट देखें । यहाँ हम फिर से कुछ अंतर देखते हैं। हारून की CROSS APPLY विधि की तुलना में मेरी विधि का "पैरामीटरयुक्त" स्वाद अब बमुश्किल 2 रीड की अगुवाई में है (बिना कैशिंग के वे समान थे)। लेकिन वास्तव में अजीब बात यह है कि पहली बार हम एक ऐसी विधि देखते हैं जो कैशिंग से नकारात्मक रूप से प्रभावित होती है: हारून की सीटीई विधि (जो पहले टेस्ट नंबर 3 के लिए सबसे अच्छी थी)। लेकिन, मैं इसका श्रेय नहीं लेने जा रहा हूं, जहां इसकी कोई वजह नहीं है, और चूंकि बिना कोचिंग के हारून की सीटीई पद्धति अभी भी मेरी पद्धति से तेज है, इसलिए कैचिंग के साथ, इस विशेष स्थिति के लिए सबसे अच्छा तरीका है आरोन का सीटीई तरीका।

निष्कर्ष कृपया नीचे की ओर अद्यतन देखें (पंक्ति के नीचे)
स्थिति जो एक द्वितीयक क्वेरी के परिणामों का बार-बार उपयोग करती है, अक्सर (लेकिन हमेशा नहीं) उन परिणामों को कैशिंग से लाभ मिलता है। लेकिन जब कैशिंग एक लाभ है, तो कहा गया है कि कैशिंग के लिए मेमोरी का उपयोग करने से अस्थायी तालिकाओं का उपयोग करने पर कुछ लाभ होता है।

प्रक्रिया

आम तौर पर

मैंने "हैडर" क्वेरी को अलग कर दिया है (यानी एस प्राप्त कर रहा है ProductID, और एक मामले में भी DaysToManufacture, Nameकुछ अक्षरों के साथ शुरुआत के आधार पर ) "विस्तार" क्वेरी (यानी TransactionIDएस और TransactionDateएस हो रही है ) से। यह अवधारणा बहुत ही सरल प्रश्न करने के लिए थी और आशावादी को उनके साथ जुड़ने पर भ्रमित होने की अनुमति नहीं थी। स्पष्ट रूप से यह हमेशा फायदेमंद नहीं होता है क्योंकि यह ऑप्टिमाइज़र को अच्छी तरह से अनुकूलित कर देता है। लेकिन जैसा कि हमने परिणामों में देखा, क्वेरी के प्रकार के आधार पर, इस पद्धति की अपनी खूबियां हैं।

इस विधि के विभिन्न स्वादों के बीच अंतर हैं:

  • स्थिरांक: मापदंडों के बजाय इनलाइन स्थिरांक के रूप में किसी भी बदली मूल्यों को प्रस्तुत करें। यह ProductIDतीनों परीक्षणों में संदर्भित होगा और टेस्ट 2 में लौटने के लिए पंक्तियों की संख्या भी होगी क्योंकि यह " DaysToManufactureउत्पाद विशेषता का पांच गुना" का एक फ़ंक्शन है । इस उप-विधि का अर्थ है कि प्रत्येक ProductIDको अपनी निष्पादन योजना प्राप्त होगी, जो कि अगर डेटा वितरण के लिए व्यापक भिन्नता है तो लाभकारी हो सकती है ProductID। लेकिन अगर डेटा वितरण में थोड़ी भिन्नता है, तो अतिरिक्त योजनाओं के निर्माण की लागत संभवतः इसके लायक नहीं होगी।

  • पैरामिट्रीकृत: कम से कम जमा ProductIDके रूप में @ProductID, कार्य योजना लागू करके कैशिंग और पुन: उपयोग के लिए अनुमति देता है। एक पैरामीटर के रूप में टेस्ट 2 के लिए लौटने के लिए पंक्तियों की चर संख्या का इलाज करने के लिए एक अतिरिक्त परीक्षण विकल्प भी है।

  • ऑप्टिमाइज़ अनजान: जब संदर्भ के ProductIDरूप में @ProductID, यदि डेटा वितरण की व्यापक विविधता है, तो एक योजना को कैश करना संभव है जो अन्य ProductIDमूल्यों पर नकारात्मक प्रभाव डालता है इसलिए यह जानना अच्छा होगा कि इस क्वेरी संकेत का उपयोग करने से कोई मदद मिलती है।

  • कैश उत्पाद:Production.Product हर बार तालिका को क्वेरी करने के बजाय , केवल उसी सूची को प्राप्त करने के लिए, क्वेरी को एक बार चलाएं (और जब हम उस पर हों, तो किसी भी ProductIDs को फ़िल्टर करें जो TransactionHistoryतालिका में भी नहीं हैं इसलिए हम किसी को बर्बाद नहीं करते हैं वहाँ संसाधन) और उस सूची को कैश करें। सूची में DaysToManufactureफ़ील्ड शामिल होना चाहिए । इस विकल्प का उपयोग करते हुए पहले निष्पादन के लिए लॉजिकल रीड्स पर थोड़ा अधिक प्रारंभिक हिट होता है, लेकिन इसके बाद यह केवल TransactionHistoryतालिका होती है जिसे क्वैरी किया जाता है।

विशेष रूप से

ठीक है, लेकिन हां, उम, कैसे एक CURSOR का उपयोग किए बिना और एक अस्थायी तालिका या टेबल चर के लिए सेट प्रत्येक परिणाम डंपिंग के बिना सभी उप-प्रश्नों को जारी करना संभव है? स्पष्ट रूप से CURSOR / Temp Table मेथड को करने से रीड्स और राइट्स में स्पष्ट रूप से प्रतिबिंबित होगा। खैर, SQLCLR :) का उपयोग करके। SQLCLR संग्रहीत कार्यविधि बनाकर, मैं परिणाम सेट खोलने में सक्षम था और अनिवार्य रूप से प्रत्येक सब-क्वेरी के परिणामों को स्ट्रीम कर सकता था, एक निरंतर परिणाम सेट के रूप में (और एकाधिक परिणाम सेट नहीं)। उत्पाद जानकारी के बाहर (यानी ProductID, Nameहै, औरDaysToManufacture), सब-क्वेरी परिणामों में से किसी को भी कहीं भी संग्रहीत नहीं किया जाना था (मेमोरी या डिस्क) और बस SQLCLR संग्रहीत कार्यविधि के मुख्य परिणाम सेट के रूप में पारित किया गया। इसने मुझे उत्पाद की जानकारी प्राप्त करने के लिए एक सरल क्वेरी करने और फिर इसके माध्यम से चक्र करने की अनुमति दी, जिसके खिलाफ बहुत ही सरल प्रश्न जारी किए TransactionHistory

और, यही कारण है कि मुझे आँकड़ों को पकड़ने के लिए SQL Server Profiler का उपयोग करना पड़ा। SQLCLR संग्रहीत कार्यविधि निष्पादन योजना नहीं लौटी, या तो "वास्तविक निष्पादन योजना शामिल करें" क्वेरी विकल्प, या जारी करके SET STATISTICS XML ON;

उत्पाद जानकारी कैशिंग के लिए, मैंने एक readonly staticजेनेरिक सूची (यानी _GlobalProductsनीचे दिए गए कोड में) का उपयोग किया। ऐसा लगता है कि संग्रह में जोड़ना readonlyविकल्प का उल्लंघन नहीं करता है , इसलिए यह कोड तब काम करता है जब असेंबली PERMISSON_SETका SAFE:) होता है, भले ही वह काउंटर-सहज हो।

उत्पन्न क्वेरी

इस SQLCLR संग्रहीत कार्यविधि द्वारा निर्मित क्वेरी निम्नानुसार हैं:

उत्पाद की जानकारी

टेस्ट नंबर 1 और 3 (कोई कैशिंग नहीं)

SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
FROM   Production.Product prod1
WHERE  prod1.Name LIKE N'[M-R]%';

टेस्ट नंबर 2 (कोई कैशिंग नहीं)

;WITH cte AS
(
    SELECT prod1.ProductID
    FROM   Production.Product prod1 WITH (INDEX(AK_Product_Name))
    WHERE  prod1.Name LIKE N'[M-R]%'
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM   Production.Product prod2
INNER JOIN cte
        ON cte.ProductID = prod2.ProductID;

टेस्ट नंबर 1, 2 और 3 (कैशिंग)

;WITH cte AS
(
    SELECT prod1.ProductID
    FROM   Production.Product prod1 WITH (INDEX(AK_Product_Name))
    WHERE  prod1.Name LIKE N'[M-R]%'
    AND    EXISTS (
                SELECT *
                FROM Production.TransactionHistory th
                WHERE th.ProductID = prod1.ProductID
                  )
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM   Production.Product prod2
INNER JOIN cte
        ON cte.ProductID = prod2.ProductID;

लेन-देन की जानकारी

टेस्ट नंबर 1 और 2 (लगातार)

SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = 977
ORDER BY th.TransactionDate DESC;

टेस्ट नंबर 1 और 2 (पैरामीटरकृत)

SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;

टेस्ट नंबर 1 और 2 (परिमापित + वैकल्पिक अंक)

SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));

टेस्ट नंबर 2 (दोनों को परिमाणित)

SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;

टेस्ट नंबर 2 (दोनों को अलग-अलग घोषित करें)

SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));

टेस्ट नंबर 3 (लगातार)

SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = 977
ORDER BY th.TransactionDate DESC, th.TransactionID DESC;

परीक्षण संख्या 3 (परिमाणित)

SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
;

परीक्षण संख्या 3 (परिमित + वैकल्पिक अंक)

SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM   Production.TransactionHistory th
WHERE  th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));

कोड

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class ObligatoryClassName
{
    private class ProductInfo
    {
        public int ProductID;
        public string Name;
        public int DaysToManufacture;

        public ProductInfo(int ProductID, string Name, int DaysToManufacture)
        {
            this.ProductID = ProductID;
            this.Name = Name;
            this.DaysToManufacture = DaysToManufacture;

            return;
        }
    }

    private static readonly List<ProductInfo> _GlobalProducts = new List<ProductInfo>();

    private static void PopulateGlobalProducts(SqlBoolean PrintQuery)
    {
        if (_GlobalProducts.Count > 0)
        {
            if (PrintQuery.IsTrue)
            {
                SqlContext.Pipe.Send(String.Concat("I already haz ", _GlobalProducts.Count,
                            " entries :)"));
            }

            return;
        }

        SqlConnection _Connection = new SqlConnection("Context Connection = true;");
        SqlCommand _Command = new SqlCommand();
        _Command.CommandType = CommandType.Text;
        _Command.Connection = _Connection;
        _Command.CommandText = @"
   ;WITH cte AS
   (
     SELECT prod1.ProductID
     FROM   Production.Product prod1 WITH (INDEX(AK_Product_Name))
     WHERE  prod1.Name LIKE N'[M-R]%'
     AND    EXISTS (
                     SELECT *
                     FROM Production.TransactionHistory th
                     WHERE th.ProductID = prod1.ProductID
                   )
   )
   SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
   FROM   Production.Product prod2
   INNER JOIN cte
           ON cte.ProductID = prod2.ProductID;
";

        SqlDataReader _Reader = null;

        try
        {
            _Connection.Open();

            _Reader = _Command.ExecuteReader();

            while (_Reader.Read())
            {
                _GlobalProducts.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
                                                    _Reader.GetInt32(2)));
            }
        }
        catch
        {
            throw;
        }
        finally
        {
            if (_Reader != null && !_Reader.IsClosed)
            {
                _Reader.Close();
            }

            if (_Connection != null && _Connection.State != ConnectionState.Closed)
            {
                _Connection.Close();
            }

            if (PrintQuery.IsTrue)
            {
                SqlContext.Pipe.Send(_Command.CommandText);
            }
        }

        return;
    }


    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void GetTopRowsPerGroup(SqlByte TestNumber,
        SqlByte ParameterizeProductID, SqlBoolean OptimizeForUnknown,
        SqlBoolean UseSequentialAccess, SqlBoolean CacheProducts, SqlBoolean PrintQueries)
    {
        SqlConnection _Connection = new SqlConnection("Context Connection = true;");
        SqlCommand _Command = new SqlCommand();
        _Command.CommandType = CommandType.Text;
        _Command.Connection = _Connection;

        List<ProductInfo> _Products = null;
        SqlDataReader _Reader = null;

        int _RowsToGet = 5; // default value is for Test Number 1
        string _OrderByTransactionID = "";
        string _OptimizeForUnknown = "";
        CommandBehavior _CmdBehavior = CommandBehavior.Default;

        if (OptimizeForUnknown.IsTrue)
        {
            _OptimizeForUnknown = "OPTION (OPTIMIZE FOR (@ProductID UNKNOWN))";
        }

        if (UseSequentialAccess.IsTrue)
        {
            _CmdBehavior = CommandBehavior.SequentialAccess;
        }

        if (CacheProducts.IsTrue)
        {
            PopulateGlobalProducts(PrintQueries);
        }
        else
        {
            _Products = new List<ProductInfo>();
        }


        if (TestNumber.Value == 2)
        {
            _Command.CommandText = @"
   ;WITH cte AS
   (
     SELECT prod1.ProductID
     FROM   Production.Product prod1 WITH (INDEX(AK_Product_Name))
     WHERE  prod1.Name LIKE N'[M-R]%'
   )
   SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
   FROM   Production.Product prod2
   INNER JOIN cte
           ON cte.ProductID = prod2.ProductID;
";
        }
        else
        {
            _Command.CommandText = @"
     SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
     FROM   Production.Product prod1
     WHERE  prod1.Name LIKE N'[M-R]%';
";
            if (TestNumber.Value == 3)
            {
                _RowsToGet = 1;
                _OrderByTransactionID = ", th.TransactionID DESC";
            }
        }

        try
        {
            _Connection.Open();

            // Populate Product list for this run if not using the Product Cache
            if (!CacheProducts.IsTrue)
            {
                _Reader = _Command.ExecuteReader(_CmdBehavior);

                while (_Reader.Read())
                {
                    _Products.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
                                                  _Reader.GetInt32(2)));
                }

                _Reader.Close();

                if (PrintQueries.IsTrue)
                {
                    SqlContext.Pipe.Send(_Command.CommandText);
                }
            }
            else
            {
                _Products = _GlobalProducts;
            }

            SqlDataRecord _ResultRow = new SqlDataRecord(
                new SqlMetaData[]{
                    new SqlMetaData("ProductID", SqlDbType.Int),
                    new SqlMetaData("Name", SqlDbType.NVarChar, 50),
                    new SqlMetaData("TransactionID", SqlDbType.Int),
                    new SqlMetaData("TransactionDate", SqlDbType.DateTime)
                });

            SqlParameter _ProductID = new SqlParameter("@ProductID", SqlDbType.Int);
            _Command.Parameters.Add(_ProductID);
            SqlParameter _RowsToReturn = new SqlParameter("@RowsToReturn", SqlDbType.Int);
            _Command.Parameters.Add(_RowsToReturn);

            SqlContext.Pipe.SendResultsStart(_ResultRow);

            for (int _Row = 0; _Row < _Products.Count; _Row++)
            {
                // Tests 1 and 3 use previously set static values for _RowsToGet
                if (TestNumber.Value == 2)
                {
                    if (_Products[_Row].DaysToManufacture == 0)
                    {
                        continue; // no use in issuing SELECT TOP (0) query
                    }

                    _RowsToGet = (5 * _Products[_Row].DaysToManufacture);
                }

                _ResultRow.SetInt32(0, _Products[_Row].ProductID);
                _ResultRow.SetString(1, _Products[_Row].Name);

                switch (ParameterizeProductID.Value)
                {
                    case 0x01:
                        _Command.CommandText = String.Format(@"
   SELECT TOP ({0}) th.TransactionID, th.TransactionDate
   FROM   Production.TransactionHistory th
   WHERE  th.ProductID = @ProductID
   ORDER BY th.TransactionDate DESC{2}
   {1};
", _RowsToGet, _OptimizeForUnknown, _OrderByTransactionID);

                        _ProductID.Value = _Products[_Row].ProductID;
                        break;
                    case 0x02:
                        _Command.CommandText = String.Format(@"
   SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
   FROM   Production.TransactionHistory th
   WHERE  th.ProductID = @ProductID
   ORDER BY th.TransactionDate DESC
   {0};
", _OptimizeForUnknown);

                        _ProductID.Value = _Products[_Row].ProductID;
                        _RowsToReturn.Value = _RowsToGet;
                        break;
                    default:
                        _Command.CommandText = String.Format(@"
   SELECT TOP ({0}) th.TransactionID, th.TransactionDate
   FROM   Production.TransactionHistory th
   WHERE  th.ProductID = {1}
   ORDER BY th.TransactionDate DESC{2};
", _RowsToGet, _Products[_Row].ProductID, _OrderByTransactionID);
                        break;
                }


                _Reader = _Command.ExecuteReader(_CmdBehavior);

                while (_Reader.Read())
                {
                    _ResultRow.SetInt32(2, _Reader.GetInt32(0));
                    _ResultRow.SetDateTime(3, _Reader.GetDateTime(1));

                    SqlContext.Pipe.SendResultsRow(_ResultRow);
                }
                _Reader.Close();
            }

        }
        catch
        {
            throw;
        }
        finally
        {
            if (SqlContext.Pipe.IsSendingResults)
            {
                SqlContext.Pipe.SendResultsEnd();
            }

            if (_Reader != null && !_Reader.IsClosed)
            {
                _Reader.Close();
            }

            if (_Connection != null && _Connection.State != ConnectionState.Closed)
            {
                _Connection.Close();
            }

            if (PrintQueries.IsTrue)
            {
                SqlContext.Pipe.Send(_Command.CommandText);
            }
        }


    }
}

टेस्ट क्वेरी

यहां परीक्षणों को पोस्ट करने के लिए पर्याप्त जगह नहीं है इसलिए मुझे एक और स्थान मिलेगा।

निष्कर्ष

कुछ परिदृश्यों के लिए, SQLCLR का उपयोग टी-एसक्यूएल में नहीं किए जा सकने वाले प्रश्नों के कुछ पहलुओं में हेरफेर करने के लिए किया जा सकता है। और अस्थायी तालिकाओं के बजाय कैशिंग के लिए मेमोरी का उपयोग करने की क्षमता है, हालांकि इसे संयमपूर्वक और सावधानी से किया जाना चाहिए क्योंकि स्मृति स्वचालित रूप से सिस्टम में वापस नहीं मिलती है। यह विधि भी ऐसी कोई चीज़ नहीं है जो तदर्थ प्रश्नों को मदद करेगी, हालाँकि इसे अधिक लचीला बनाना संभव है, जैसा कि मैंने यहाँ दिखाया है कि पैरामीटर को दर्ज़ करके क्वेरीज़ के अधिक पहलुओं को निष्पादित किया जा रहा है।


अपडेट करें

अतिरिक्त परीक्षण
मेरा मूल परीक्षण जिसमें TransactionHistoryनिम्नलिखित परिभाषा का उपयोग करने पर एक सहायक सूचकांक शामिल था :

ProductID ASC, TransactionDate DESC

मैंने उस समय TransactionId DESCअंत में निर्णय लेने का निर्णय लिया था, जिसमें यह अनुमान लगाया गया था कि यह टेस्ट नंबर 3 की मदद कर सकता है (जो सबसे हाल ही में टाई-ब्रेकिंग को निर्दिष्ट TransactionIdकरता है, "सबसे हाल का" माना जाता है क्योंकि स्पष्ट रूप से नहीं बताया गया है, लेकिन हर कोई लगता है इस धारणा पर सहमत होने के लिए), एक अंतर बनाने के लिए पर्याप्त संबंध नहीं होने की संभावना है।

लेकिन, तब हारून ने एक सहायक सूचकांक के साथ संन्यास लिया जिसमें यह शामिल था TransactionId DESCऔर पाया गया कि CROSS APPLYविधि तीनों परीक्षणों में विजेता थी। यह मेरे परीक्षण से अलग था जिसने संकेत दिया कि टेस्ट नंबर 3 के लिए सीटीई पद्धति सबसे अच्छी थी (जब कोई कैशिंग का उपयोग नहीं किया गया था, जो हारून के परीक्षण को प्रतिबिंबित करता है)। यह स्पष्ट था कि एक अतिरिक्त भिन्नता थी जिसे जांचने की आवश्यकता थी।

मैंने करंट सपोर्टिंग इंडेक्स को हटा दिया, साथ में एक नया बनाया TransactionId, और प्लान कैश को मंजूरी दे दी (बस सुनिश्चित किया जाए):

DROP INDEX [IX_TransactionHistoryX] ON Production.TransactionHistory;

CREATE UNIQUE INDEX [UIX_TransactionHistoryX]
    ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC, TransactionID DESC)
    WITH (FILLFACTOR = 100);

DBCC FREEPROCCACHE WITH NO_INFOMSGS;

मैंने टेस्ट नंबर 1 को फिर से चलाया और परिणाम उम्मीद के मुताबिक ही थे। मैंने तब टेस्ट नंबर 3 को फिर से चलाया और परिणाम वास्तव में बदल गए:

टेस्ट 3 परिणाम-सपोर्टिंग इंडेक्स के साथ (TransactionId DESC के साथ)
उपरोक्त परिणाम मानक, गैर-कैशिंग परीक्षण के लिए हैं। इस बार, न केवल CROSS APPLYसीटीई (आरोन के परीक्षण के संकेत के रूप में) को हराया, बल्कि एसक्यूसीएलआर की खरीद ने 30 रीड्स (वू हू) से बढ़त हासिल की।

टेस्ट 3 परिणाम-सपोर्टिंग इंडेक्स के साथ (TransactionId DESC के साथ) और कैशिंग
उपरोक्त परिणाम कैशिंग सक्षम के साथ परीक्षण के लिए हैं। इस बार सीटीई के प्रदर्शन में गिरावट नहीं है, हालांकि CROSS APPLYअभी भी यह धड़कता है। हालाँकि, अब SQLCLR खरीद 23 रीड्स (वू हू, फिर से) द्वारा लीड लेती है।

दूर ले जाओ

  1. उपयोग करने के लिए विभिन्न विकल्प हैं। यह कोशिश करना सबसे अच्छा है क्योंकि उनमें से प्रत्येक के पास अपनी ताकत है। यहां किए गए परीक्षण सभी परीक्षणों के दौरान सर्वश्रेष्ठ और सबसे खराब प्रदर्शन करने वालों (समर्थन सूचकांक के साथ) के बीच रीड्स और अवधि दोनों में एक छोटा सा बदलाव दिखाते हैं; रीड्स में भिन्नता लगभग 350 है और अवधि 55 एमएस है। जबकि SQLCLR प्रॉपर्टी ने सभी लेकिन 1 टेस्ट (रीड्स के संदर्भ में) में जीत हासिल की, केवल कुछ रीड्स को सहेजना आमतौर पर SQLCLR रूट के रखरखाव लागत के लायक नहीं है। लेकिन AdventureWorks2012 में, Productतालिका में केवल 504 पंक्तियाँ हैं और TransactionHistoryकेवल 113,443 पंक्तियाँ हैं। इन विधियों में प्रदर्शन अंतर संभवतः अधिक स्पष्ट हो जाता है क्योंकि पंक्ति की संख्या बढ़ जाती है।

  2. जबकि यह प्रश्न पंक्तियों के एक विशेष सेट को प्राप्त करने के लिए विशिष्ट था, पर यह ध्यान नहीं दिया जाना चाहिए कि प्रदर्शन का सबसे बड़ा कारक अनुक्रमण था और विशेष SQL नहीं। वास्तव में सबसे अच्छा तरीका निर्धारित करने से पहले एक अच्छे सूचकांक की आवश्यकता होती है।

  3. यहां पाया जाने वाला सबसे महत्वपूर्ण सबक CROSS APPLY बनाम CTE बनाम SQLCLR के बारे में नहीं है: यह परीक्षण के बारे में है। मत मानो। कई लोगों से विचार प्राप्त करें और जितने परिदृश्य आप कर सकते हैं उतने परीक्षण करें।


2
लागू होने से संबंधित अतिरिक्त तार्किक रीड के कारण के लिए मिकेल के उत्तर के लिए मेरा संपादन देखें।
पॉल व्हाइट

18

APPLY TOPया ROW_NUMBER()? उस मामले पर संभवतः और क्या कहा जा सकता है?

मतभेदों की एक संक्षिप्त पुनरावृत्ति और इसे वास्तव में छोटा रखने के लिए मैं केवल विकल्प 2 की योजनाएं दिखाऊंगा और मैंने सूचकांक को जोड़ा है Production.TransactionHistory

create index IX_TransactionHistoryX on 
  Production.TransactionHistory(ProductID, TransactionDate)

row_number()क्वेरी :.

with C as
(
  select T.TransactionID,
         T.TransactionDate,
         P.DaysToManufacture,
         row_number() over(partition by P.ProductID order by T.TransactionDate desc) as rn
  from Production.Product as P
    inner join Production.TransactionHistory as T
      on P.ProductID = T.ProductID
  where P.Name >= N'M' and
        P.Name < N'S'
)
select C.TransactionID,
       C.TransactionDate
from C
where C.rn <= 5 * C.DaysToManufacture;

यहाँ छवि विवरण दर्ज करें

apply topसंस्करण:

select T.TransactionID, 
       T.TransactionDate
from Production.Product as P
  cross apply (
              select top(cast(5 * P.DaysToManufacture as bigint))
                T.TransactionID,
                T.TransactionDate
              from Production.TransactionHistory as T
              where P.ProductID = T.ProductID
              order by T.TransactionDate desc
              ) as T
where P.Name >= N'M' and
      P.Name < N'S';

यहाँ छवि विवरण दर्ज करें

इनमें से मुख्य अंतर यह है कि apply topनेस्टेड लूप के नीचे शीर्ष अभिव्यक्ति पर फ़िल्टर शामिल होते हैं जहां शामिल होने के row_numberबाद संस्करण फ़िल्टर करता है। इसका मतलब है कि Production.TransactionHistoryवास्तव में आवश्यक है की तुलना में अधिक पढ़ रहे हैं ।

यदि केवल शामिल होने से पहले निचली शाखा के नीचे पंक्तियों की गणना के लिए जिम्मेदार ऑपरेटरों को धक्का देने का एक तरीका मौजूद था, तो row_numberसंस्करण बेहतर हो सकता है।

तो apply row_number()संस्करण दर्ज करें ।

select T.TransactionID, 
       T.TransactionDate
from Production.Product as P
  cross apply (
              select T.TransactionID,
                     T.TransactionDate
              from (
                   select T.TransactionID,
                          T.TransactionDate,
                          row_number() over(order by T.TransactionDate desc) as rn
                   from Production.TransactionHistory as T
                   where P.ProductID = T.ProductID
                   ) as T
              where T.rn <= cast(5 * P.DaysToManufacture as bigint)
              ) as T
where P.Name >= N'M' and
      P.Name < N'S';

यहाँ छवि विवरण दर्ज करें

जैसा कि आप देख सकते हैं apply row_number()कि बहुत अधिक apply topकेवल थोड़ा और अधिक जटिल है। निष्पादन का समय भी उसी या थोड़ा धीमा के बारे में है।

तो मैंने ऐसा जवाब देने की जहमत क्यों उठाई जो हमारे पास पहले से ही नहीं है? ठीक है, आपके पास वास्तविक दुनिया में प्रयास करने के लिए एक और चीज है और वास्तव में रीड्स में अंतर है। एक कि मेरे पास * के लिए स्पष्टीकरण नहीं है।

APPLY - ROW_NUMBER
(961 row(s) affected)
Table 'TransactionHistory'. Scan count 115, logical reads 230, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 15, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

APPLY - TOP
(961 row(s) affected)
Table 'TransactionHistory'. Scan count 115, logical reads 268, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 15, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

जब मैं इस पर हूं तो मैं दूसरे row_number()संस्करण में भी फेंक सकता हूं कि कुछ मामलों में जाने का रास्ता हो सकता है। वे कुछ निश्चित मामले होंगे जब आप उम्मीद करेंगे कि वास्तव में आपको सबसे अधिक पंक्तियों की आवश्यकता है Production.TransactionHistoryक्योंकि यहाँ से आपको एक विलय मिल जाता है Production.Productऔर प्रगणित हो जाता है Production.TransactionHistory

with C as
(
  select T.TransactionID,
         T.TransactionDate,
         T.ProductID,
         row_number() over(partition by T.ProductID order by T.TransactionDate desc) as rn
  from Production.TransactionHistory as T
)
select C.TransactionID,
       C.TransactionDate
from C
 inner join Production.Product as P
      on P.ProductID = C.ProductID
where P.Name >= N'M' and
      P.Name < N'S' and
      C.rn <= 5 * P.DaysToManufacture;

यहाँ छवि विवरण दर्ज करें

सॉर्ट ऑपरेटर के बिना उपरोक्त आकार प्राप्त करने के लिए आपको TransactionDateअवरोही द्वारा ऑर्डर करने के लिए सहायक सूचकांक को बदलना होगा ।

create index IX_TransactionHistoryX on 
  Production.TransactionHistory(ProductID, TransactionDate desc)

* संपादित करें: अतिरिक्त तार्किक रीड्स नेस्टेड लूप्स के कारण होते हैं जो कि एप -टॉप के साथ उपयोग किए जाते हैं। तार्किक रीड्स की समान संख्या प्राप्त करने के लिए आप इसे undoc'd TF 8744 (और / या 9115 बाद के संस्करणों में) के साथ अक्षम कर सकते हैं। सही परिस्थितियों में लागू टॉप-अप विकल्प का एक फायदा प्रीफेटिंग हो सकता है। - पॉल व्हाइट


11

मैं आमतौर पर सीटीई और विंडोिंग फ़ंक्शन के संयोजन का उपयोग करता हूं। आप इस उत्तर को निम्न प्रकार से प्राप्त कर सकते हैं:

;WITH GiveMeCounts
AS (
    SELECT CustomerID
        ,OrderDate
        ,TotalAmt

        ,ROW_NUMBER() OVER (
            PARTITION BY CustomerID ORDER BY 
            --You can change the following field or sort order to whatever you'd like to order by.
            TotalAmt desc
            ) AS MySeqNum
    )
SELECT CustomerID, OrderDate, TotalAmt
FROM GiveMeCounts
--Set n per group here
where MySeqNum <= 10

अतिरिक्त क्रेडिट हिस्से के लिए, जहां विभिन्न समूह अलग-अलग संख्या में पंक्तियों को वापस करना चाहते हैं, आप एक अलग तालिका का उपयोग कर सकते हैं। भौगोलिक मानदंड जैसे राज्य का उपयोग करते हुए कहते हैं:

+-------+-----------+
| State | MaxSeqnum |
+-------+-----------+
| AK    |        10 |
| NY    |         5 |
| NC    |        23 |
+-------+-----------+

इसे प्राप्त करने के लिए जहां मूल्य भिन्न हो सकते हैं, आपको अपने CTE को इसके समान राज्य तालिका में शामिल करना होगा:

SELECT [CustomerID]
    ,[OrderDate]
    ,[TotalAmt]
    ,[State]
FROM GiveMeCounts gmc
INNER JOIN StateTable st ON gmc.[State] = st.[State]
    AND gmc.MySeqNum <= st.MaxSeqNum
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.