প্রতি গ্রুপে এন সারি পুনরুদ্ধার করা হচ্ছে


88

আমার প্রায়শই একটি ফলাফল সেটে প্রতিটি গ্রুপ থেকে বেশ কয়েকটি সারি নির্বাচন করা প্রয়োজন।

উদাহরণস্বরূপ, আমি গ্রাহক প্রতি 'এন' সর্বোচ্চ বা সর্বনিম্ন সাম্প্রতিক ক্রমের মানগুলি তালিকাবদ্ধ করতে চাই।

আরও জটিল ক্ষেত্রে, তালিকার জন্য সারিগুলির সংখ্যা গ্রুপ অনুযায়ী পৃথক হতে পারে (গ্রুপিং / প্যারেন্ট রেকর্ডের একটি বৈশিষ্ট্যের দ্বারা সংজ্ঞায়িত)। এই অংশটি অবশ্যই অতিরিক্ত creditণের জন্য alচ্ছিক এবং কোনও উত্তর দেওয়া থেকে লোককে বিরত রাখতে নয়।

এসকিউএল সার্ভার ২০০৫ এবং এর পরে এই ধরণের সমস্যা সমাধানের মূল বিকল্পগুলি কী কী? প্রতিটি পদ্ধতির প্রধান সুবিধা এবং অসুবিধাগুলি কী কী?

অ্যাডভেঞ্চার ওয়ার্কস উদাহরণ (স্পষ্টতার জন্য, alচ্ছিক)

  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 এ নেমে যায় So সুতরাং আমরা ইতিমধ্যে 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;

দুটি পরিকল্পনা - বেসিক শীর্ষ ow রোউনাম

আপনি খেয়াল করবেন যে দ্বিতীয় ( 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 টি এবং ট্রানজেকশনহিটরির বিপরীতে 360 টি দেয়।

আবেদন পরিকল্পনা

ব্যবহার করে ROW_NUMBER(), পদ্ধতিটি হ'ল ধারাটিতে ব্যবহার PARTITION BYকরা OVER, যাতে আমরা প্রতিটি পণ্যের জন্য নম্বরটি পুনরায় চালু করি। এটি তখন আগের মতো ফিল্টার করা যায়। পরিকল্পনাটি একেবারেই আলাদা হয়ে যায়। লজিক্যাল রিডগুলি লেনদেনের ইতিহাসে প্রায় 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 পরিকল্পনা

তাত্পর্যপূর্ণ, যদিও, এই পরিকল্পনায় একটি ব্যয়বহুল বাছাই করা অপারেটর রয়েছে। মার্জ যোগ দিন লেনদেনের ইতিহাসে সারিগুলির ক্রম বজায় রাখার মতো মনে হয় না, পটভূমিগুলি সন্ধানের জন্য ডেটাটি অবশ্যই রিসর্ট করতে হবে। এটি কম পড়া, তবে এই ব্লক করা বাছাই করা বেদনাদায়ক অনুভব করতে পারে। ব্যবহার করে APPLY, নেস্টেড লুপ প্রথম সারিতে খুব দ্রুত ফিরে আসবে, মাত্র কয়েকটি পড়ার পরে, তবে একটি বাছাইয়ের সাথে, ROW_NUMBER()বেশিরভাগ কাজ শেষ হয়ে গেলে সারিগুলি কেবল ফিরে আসবে।

মজার বিষয় হল, যদি ROW_NUMBER()ক্যোয়ারী INNER JOINপরিবর্তে ব্যবহার করে LEFT JOINতবে একটি আলাদা পরিকল্পনা সামনে আসে।

INNER যোগদানের সাথে ROW_NUMBER ()

এই পরিকল্পনাটি ঠিক যেমন নেস্টেড লুপ ব্যবহার করে APPLY। তবে শীর্ষস্থানীয় অপারেটর নেই, সুতরাং এটি প্রতিটি পণ্যের জন্য সমস্ত লেনদেনকে টান দেয় এবং আগের চেয়ে অনেক বেশি পঠন ব্যবহার করে - 492 ট্রানজেকশনহিসটরির বিপরীতে পড়ে। এখানে মার্জ জয়েন বিকল্পটি না বেছে নেওয়ার কোনও উপযুক্ত কারণ নেই, তাই আমার ধারণা, পরিকল্পনাটি 'গুড ইনফ' হিসাবে বিবেচিত হয়েছিল। তবুও - এটি ব্লক করে না, যা দুর্দান্ত - ঠিক তত সুন্দর নয় 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;

তবে যোগদানকারী অপারেটরটি 'অভ্যন্তরীণ যোগদান' এর পরিবর্তে 'বাম আউটার জয়েন' বলে says লেনদেনের ইতিহাসের টেবিলের বিপরীতে পঠনের সংখ্যা এখনও 500 এর নিচে রয়েছে।

H.ProductID এর পরিবর্তে p.ProducctID- এ পার্টিশন

যাইহোক - হাতে প্রশ্নে ফিরে ...

আপনি বেছে নিতে এবং চয়ন করতে পারেন এমন দুটি বিকল্পের সাথে আমরা প্রথম প্রশ্নের উত্তর দিয়েছি । ব্যক্তিগতভাবে, আমি 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';

এবং উভয় পরিকল্পনা প্রায় আগের মত একই!

পরিবর্তনশীল সারি

আবার, আনুমানিক ব্যয়গুলি উপেক্ষা করুন - তবে আমি এখনও শীর্ষের দৃশ্য পছন্দ করি, কারণ এটি অনেক বেশি সহজ এবং পরিকল্পনার কোনও ব্লকিং অপারেটর নেই। শূন্যের সংখ্যা বেশি হওয়ায় ট্রানজেকশন ইতিহাসে পাঠগুলি কম 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মান কাজ করছে । লেনদেনের ইতিহাস টেবিল থেকে ডেটা টানছে এমন শাখায় এই মানটি প্রবেশ করা হচ্ছে না, এটি মার্জ জোনে ব্যবহৃত হচ্ছে। অবশিষ্ট হিসাবে।

লুক্কায়িত অবশিষ্ট!

সুতরাং মার্জ যোগ দিন সমস্ত সারিগুলি গ্রাস করছে, কেবল প্রথমে অবশ্য অনেকগুলি প্রয়োজনীয়। তবে সেগুলি এবং তারপরে একটি অবশিষ্ট চেক করছে। লেনদেনের সংখ্যা বৃদ্ধি পাওয়ায় এটি বিপজ্জনক। আমি এই দৃশ্যের কোনও অনুরাগী নই - মার্জ জোনেসের অবশিষ্ট অনুমানগুলি দ্রুত বাড়তে পারে। আমি দৃশ্যকে কেন পছন্দ করি তার আর একটি কারণ APPLY/TOP

বিশেষ ক্ষেত্রে যেখানে এটি ঠিক এক সারি আছে, জন্য 3 প্রশ্ন , আমরা স্পষ্টত একই প্রশ্নের ব্যবহার করতে পারেন, কিন্তু 1পরিবর্তে 5। তবে তারপরে আমাদের একটি অতিরিক্ত বিকল্প রয়েছে যা নিয়মিত সমষ্টি ব্যবহার করা।

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

এর মতো একটি ক্যোয়ারী একটি দরকারী সূচনা হবে এবং আমরা সহজেই টাই-ব্রেকের উদ্দেশ্যে ট্রানজেকশন আইডিটি বের করার জন্য এটি সংশোধন করতে পারি (এমন একটি সংক্ষেপণ ব্যবহার করে যা পরে ভেঙে ফেলা হবে), তবে আমরা হয় পুরো সূচকটি দেখি, বা আমরা পণ্য দ্বারা পণ্য ডুব, এবং আমরা সত্যিই আমাদের এই পরিস্থিতিতে আগে ছিল একটি বড় উন্নতি পেতে না।

তবে আমার উল্লেখ করা উচিত যে আমরা এখানে একটি নির্দিষ্ট দৃশ্যের দিকে তাকিয়ে আছি। সত্যিকারের ডেটা সহ এবং একটি সূচক কৌশল সহ যা আদর্শ নাও হতে পারে, মাইলেজ যথেষ্ট পরিমাণে পৃথক হতে পারে। আমরা APPLYএখানে দেখেছি এটি সত্য যে সত্ত্বেও এটি কিছু পরিস্থিতিতে ধীর হতে পারে। এটি খুব কমই ব্লক করে, কারণ এটি নেস্টেড লুপগুলি ব্যবহার করার প্রবণতা রয়েছে, যা অনেক লোক (আমার অন্তর্ভুক্ত) খুব আকর্ষণীয় বলে মনে হয়।

আমি এখানে সমান্তরালতা অন্বেষণ করার চেষ্টা করিনি, বা খুব শক্তিশালী প্রশ্ন 3 তে ডুব দিয়েছি, যা আমি একটি বিশেষ কেস হিসাবে দেখছি যা মানুষ বিরল এবং বিভাজনের জটিলতার উপর ভিত্তি করে খুব কমই চায়। এখানে বিবেচনা করার প্রধান বিষয় হ'ল এই দুটি বিকল্প দুটিই খুব শক্ত।

আমি পছন্দ APPLY। এটি পরিষ্কার, এটি শীর্ষ অপারেটরটি ভালভাবে ব্যবহার করে এবং এটি খুব কমই ব্লক হওয়ার কারণ হয়ে থাকে।


44

এসকিউএল সার্ভার 2005 এ এটি করার সাধারণ উপায়টি হল একটি সিটিই এবং উইন্ডোটিং ফাংশন ব্যবহার করা। প্রতি গ্রুপে শীর্ষ এনের জন্য আপনি কেবল 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কারণ তারা যুক্তিটিকে আরও ভালভাবে পৃথক করে এবং আরও স্বজ্ঞাত (আমার কাছে)। সাধারণভাবে (এই ক্ষেত্রে এবং আমার সাধারণ অভিজ্ঞতা উভয়ই), সিটিই পদ্ধতির আরও কার্যকর পরিকল্পনা তৈরি করা হয় (নীচে উদাহরণগুলি) তবে এটি সর্বজনীন সত্য হিসাবে নেওয়া উচিত নয় - আপনার সূচী পরিবর্তন হয়েছে বা বিশেষত যদি আপনার পরিস্থিতিগুলি সর্বদা পরীক্ষা করা উচিত তথ্য উল্লেখযোগ্যভাবে skew হয়েছে।


অ্যাডভেঞ্চার ওয়ার্কস উদাহরণ - কোনও পরিবর্তন ছাড়াই

  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';

রানটাইম মেট্রিকগুলিতে এই দুটির তুলনা:

এখানে চিত্র বর্ণনা লিখুন

সিটিই / OVER()পরিকল্পনা:

এখানে চিত্র বর্ণনা লিখুন

CROSS APPLY প্ল্যান:

এখানে চিত্র বর্ণনা লিখুন

সিটিই পরিকল্পনা আরও জটিল দেখায় তবে এটি আসলে আরও কার্যকর efficient আনুমানিক ব্যয়% সংখ্যাগুলিতে সামান্য মনোযোগ দিন, তবে আরও গুরুত্বপূর্ণ প্রকৃত পর্যবেক্ষণগুলিতে মনোনিবেশ করুন , যেমন কম পড়া এবং অনেক কম সময়কাল। আমি এগুলিও সমান্তরালতা ছাড়াই চালিয়েছি, এবং এটি কোনও পার্থক্য ছিল না। রানটাইম মেট্রিক্স এবং সিটিই পরিকল্পনা ( CROSS APPLYপ্ল্যানটি একই ছিল):

এখানে চিত্র বর্ণনা লিখুন

এখানে চিত্র বর্ণনা লিখুন

  1. আবার একই, তবে nপণ্য প্রতি ইতিহাসের রেখাগুলির সাথে , যেখানে পণ্যের গুণাবলীর nপাঁচগুণ বেশি DaysToManufacture

এখানে খুব ছোটখাটো পরিবর্তন দরকার। সিটিইর জন্য আমরা অভ্যন্তরীণ কোয়েরিতে একটি কলাম যুক্ত করতে পারি এবং বাইরের ক্যোয়ারিতে ফিল্টার করতে পারি; জন্য 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()পরিকল্পনা:

এখানে চিত্র বর্ণনা লিখুন

একক থ্রেডেড সিটিই / OVER()পরিকল্পনা:

এখানে চিত্র বর্ণনা লিখুন

CROSS APPLY প্ল্যান:

এখানে চিত্র বর্ণনা লিখুন

  1. একই, বিশেষ ক্ষেত্রে যেখানে পণ্য প্রতি প্রতি এক হিস্ট্রি লাইন প্রয়োজন (একক সাম্প্রতিক এন্ট্রি TransactionDate, টাই-ব্রেক) TransactionID

আবার, এখানে ছোটখাটো পরিবর্তন। কোটে সমাধান, আমরা যোগ 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()পরিকল্পনা:

এখানে চিত্র বর্ণনা লিখুন

একক থ্রেডেড সিটিই / ওভার () পরিকল্পনা:

এখানে চিত্র বর্ণনা লিখুন

CROSS APPLY প্ল্যান:

এখানে চিত্র বর্ণনা লিখুন

উইন্ডোটিং ফাংশনগুলি সর্বদা সেরা বিকল্প নয় (একবারে যান COUNT(*) OVER()), এবং প্রতি গ্রুপ সমস্যা হিসাবে এন সারিগুলি সমাধান করার জন্য কেবলমাত্র দুটি পন্থা নয়, তবে এই নির্দিষ্ট ক্ষেত্রে - স্কিমা, বিদ্যমান সূচী এবং ডেটা বিতরণ দেওয়া - সমস্ত অর্থবহ অ্যাকাউন্টের মাধ্যমে সিটিই আরও ভাল করেছে।


অ্যাডভেঞ্চার ওয়ার্কের উদাহরণ - সূচিগুলি যুক্ত করার নমনীয়তা সহ

যাইহোক, আপনি যদি একটি সমর্থনকারী সূচক যোগ করেন, তবে পলের মতো একই মন্তব্য করেছেন তবে ২ য় এবং ৩ য় কলামের আদেশ দিয়েছেন DESC:

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

আপনি চারদিকে আসলে আরও অনেক বেশি অনুকূল পরিকল্পনা পাবেন এবং CROSS APPLYতিনটি ক্ষেত্রেই মেট্রিকগুলি এই পদ্ধতির পক্ষে যেতে চাইবেন :

এখানে চিত্র বর্ণনা লিখুন

যদি এটি আমার উত্পাদনের পরিবেশ ছিল তবে আমি সম্ভবত এই ক্ষেত্রে সময়কাল নিয়ে সন্তুষ্ট থাকতাম এবং আরও অনুকূলিতকরণের জন্য বিরক্ত করতাম না।


এটি এসকিউএল সার্ভার 2000-এ খুব কুরুচিপূর্ণ ছিল, যা সমর্থন করে না APPLYবা OVER()ধারাটি দেয় না।


24

ডিবিএমএসে মাইএসকিউএলের মতো উইন্ডো ফাংশন নেই বা CROSS APPLY, এটি করার উপায় হ'ল মানক এসকিউএল (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 ;

দ্রষ্টব্য: মাইএসকিউএল এর পরিবর্তে AND o.OrderID IN (SELECT TOP(@top) oi.OrderID ...)একটি ব্যবহার করবে AND o.OrderDate >= (SELECT oi.OrderDate ... LIMIT 1 OFFSET (@top - 1))। এসকিউএল-সার্ভার FETCH / OFFSET2012 সংস্করণে সিনট্যাক্স যুক্ত করেছে। এখানে প্রশ্নগুলি IN (TOP...)পূর্ববর্তী সংস্করণগুলির সাথে কাজ করার জন্য সামঞ্জস্য করা হয়েছিল।


21

আমি এই প্রযুক্তিটি অন্যদের সাথে কীভাবে তুলনা করব তা দেখার জন্য আমি কিছুটা ভিন্ন পন্থা নিয়েছিলাম, কারণ বিকল্পগুলি থাকা ভাল, তাই না?

টেস্টিং

আমরা কীভাবে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে একে অপামের বিপরীতে টানতে চাই না। " আমি তিনটি সেট পরীক্ষা করেছি:

  1. প্রথম সেটটি কোনও ডিবি পরিবর্তন ছাড়াই চলে
  2. দ্বিতীয় সেটটি TransactionDateবিরোধী-ভিত্তিক প্রশ্নের সমর্থনে একটি সূচক তৈরি হওয়ার পরে চলেছিল Production.TransactionHistory
  3. তৃতীয় সেটটি কিছুটা আলাদা ধারণা নিয়েছে। যেহেতু তিনটি পরীক্ষাই একই পণ্যের তালিকার বিরুদ্ধে ছিল, আমরা যদি সেই তালিকাটি ক্যাশে করি তবে কী হবে? আমার পদ্ধতিটি ইন-মেমরি ক্যাশে ব্যবহার করে অন্য পদ্ধতিগুলি সমমানের টেম্প টেবিল ব্যবহার করে। দ্বিতীয় সেট পরীক্ষার জন্য তৈরি সহায়ক সূচকটি এই পরীক্ষার সেটগুলির জন্য এখনও বিদ্যমান।

অতিরিক্ত পরীক্ষার বিবরণ:

  • AdventureWorks2012এসকিউএল সার্ভার ২০১২, এসপি 2 (বিকাশকারী সংস্করণ) এ পরীক্ষা করা হয়েছিল ।
  • প্রতিটি পরীক্ষার জন্য আমি কার উত্তরটি দিয়েছিলাম এবং এটি কোন বিশেষ কোয়েরি থেকে লেবেল দিয়েছিল।
  • আমি অনুসন্ধানের বিকল্পগুলির "মৃত্যুর পরে ফলাফলগুলি বাতিল করুন" বিকল্পটি ব্যবহার করেছি used ফলাফল।
  • Please note that for the first two sets of tests, the RowCounts appear to be "off" for my method. This is due my method being a manual implementation of what CROSS APPLY is doing: it runs the initial query against Production.Product and gets 161 rows back, which it then uses for the queries against Production.TransactionHistory. Hence, the RowCount values for my entries are always 161 more than the other entries. In the third set of tests (with caching) the row counts are the same for all methods.
  • আমি এসকিউএল সার্ভার প্রোফাইলারকে কার্যকর করার পরিকল্পনার উপর নির্ভর না করে স্ট্যাটাস ক্যাপচার করতে ব্যবহার করি। হারুন এবং মিকেল ইতিমধ্যে তাদের প্রশ্নের পরিকল্পনাগুলি দেখিয়ে একটি দুর্দান্ত কাজ করেছেন এবং সেই তথ্যটি পুনরুত্পাদন করার দরকার নেই। এবং আমার পদ্ধতির উদ্দেশ্যটি হল এমন প্রশ্নগুলিকে এমন একটি সহজ আকারে হ্রাস করা যা এটি সত্যিই গুরুত্ব দেয় না। প্রোফাইলার ব্যবহারের অতিরিক্ত কারণ রয়েছে তবে পরে তা উল্লেখ করা হবে।
  • Name >= N'M' AND Name < N'S'কনস্ট্রাক্টটি ব্যবহার করার পরিবর্তে , আমি ব্যবহার করতে বেছে নিয়েছি Name LIKE N'[M-R]%'এবং এসকিউএল সার্ভার তাদের সাথে একই আচরণ করে।

ফলাফলগুলো

কোনও সমর্থন সূচক নেই

এটি মূলত অ্যাডভেঞ্চার ওয়ার্কস ২০১২-এর বাইরে is সব ক্ষেত্রেই আমার পদ্ধতিটি অন্য কয়েকটির থেকে পরিষ্কারভাবে ভাল তবে শীর্ষ 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);

এই সূচকটি একবার এলে ফলাফল কিছুটা বদলে যায় change

পরীক্ষা 1 পরীক্ষার 1 ফলাফল-সহকারী সূচক সহ
এই বারটি আমার পদ্ধতি যা সামনে আসে, অন্তত লজিকাল রিডসের ক্ষেত্রে। CROSS APPLYপদ্ধতি, পূর্বে টেস্ট 1 খারাপ অভিনয়কারী, স্থিতিকাল ময়দানে জিতেছে এবং এমনকি কোটে পদ্ধতি beats লজিক্যাল রাউন্ডআপ উপর।

টেস্ট 2 পরীক্ষার 2 ফলাফল সহকারী সূচক সহ
এবার মিকেলের প্রথম apply row_number()পদ্ধতি হ'ল রিডস দেখার সময় এটিই বিজয়ী, যদিও এর আগে এটি সবচেয়ে খারাপ অভিনেতা ছিল। আর এখন আমার পদ্ধতিটি রিডস দেখার সময় খুব কাছাকাছি দ্বিতীয় স্থানে চলে আসে। আসলে, সিটিই পদ্ধতির বাইরে, বাকিগুলি সমস্তই রিডসের ক্ষেত্রে মোটামুটি কাছাকাছি।

টেস্ট 3 পরীক্ষার 3 ফলাফল সহকারী সূচক সহ
এখানে সিটিই এখনও বিজয়ী, তবে এখন সূচি তৈরির আগে বিদ্যমান তাত্পর্যপূর্ণ পার্থক্যের তুলনায় অন্যান্য পদ্ধতির মধ্যে পার্থক্য সবেমাত্র লক্ষণীয়।

উপসংহার
আমার পদ্ধতির প্রয়োগযোগ্যতা এখন আরও স্পষ্ট, যদিও যথাযথ সূচী স্থানে না রাখার ক্ষেত্রে এটি কম স্বচ্ছন্দ।

সমর্থন সূচক এবং ক্যাচিং সহ

পরীক্ষার এই সেটটির জন্য আমি ক্যাশে ব্যবহার করেছি কারণ, ভাল, কেন না? আমার পদ্ধতিটি ইন-মেমরি ক্যাশে ব্যবহারের অনুমতি দেয় যা অন্যান্য পদ্ধতি অ্যাক্সেস করতে পারে না। সুতরাং ন্যায়বিচারের জন্য, আমি নিম্নলিখিত টেম্প টেবিলটি তৈরি করেছি যা Product.Productতিনটি পরীক্ষার জুড়ে other অন্যান্য পদ্ধতিতে সমস্ত রেফারেন্সের জায়গায় ব্যবহার করা হয়েছিল । DaysToManufactureক্ষেত্রটি কেবল টেস্ট নম্বর 2 এ ব্যবহৃত হয়, তবে একই টেবিলটি ব্যবহার করার জন্য এসকিউএল স্ক্রিপ্টগুলির মধ্যে সামঞ্জস্য থাকা আরও সহজ ছিল এবং এটি এটির জন্য ক্ষতি করে নি।

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 ফলাফল-সমর্থনকারী সূচক এবং ক্যাশে সহ
এখানে এখন আমি লাইনআপের মধ্যে একটি পার্থক্য দেখতে পাচ্ছি যেহেতু আমার পদ্ধতিটি সবে সামনে এগিয়ে আসে, মিকেলের প্রথম apply row_number()পদ্ধতির চেয়ে কেবল 2 টি আরও ভাল পড়েন , যখন ক্যাচিং ছাড়াই আমার পদ্ধতিটি 4 টি পড়ার পিছনে ছিল।

পরীক্ষা 3 পরীক্ষার 3 ফলাফল সহকারী সূচক এবং ক্যাশে সহ
দয়া করে নীচের দিকে (লাইনের নীচে) আপডেট দেখুন । এখানে আমরা আবার কিছু পার্থক্য দেখতে পাই। আমার পদ্ধতির "প্যারামিটারাইজড" গন্ধটি এখন অ্যারনের ক্রস অ্যাপ্লাই পদ্ধতির তুলনায় 2 রিডের তুলনায় সবেমাত্র নেতৃত্বে রয়েছে (কোনও ক্যাশে ছাড়াই তারা সমান ছিল না)। তবে সত্যিই আশ্চর্যের বিষয় হ'ল প্রথমবারের মতো আমরা এমন একটি পদ্ধতি দেখি যা ক্যাচিংয়ের মাধ্যমে নেতিবাচকভাবে প্রভাবিত হয়: হারুনের সিটিই পদ্ধতি (যা আগে টেস্ট নম্বর 3 এর জন্য সেরা ছিল)। তবে, যেখানে creditণ নেই সেখানে আমি ক্রেডিট নিতে যাচ্ছি না এবং যেহেতু ক্যাচিং ছাড়াই হারুনের সিটিই পদ্ধতিটি আমার পদ্ধতিটি এখানে ক্যাচিংয়ের তুলনায় আরও দ্রুত, তাই এই নির্দিষ্ট পরিস্থিতির জন্য সেরা পদ্ধতির উপস্থিতি হারুনের সিটিই পদ্ধতি হিসাবে দেখা যায়।

উপসংহার দয়া করে নীচের দিকে আপডেটটি দেখুন (রেখার নীচে) এমন
পরিস্থিতি যা একটি গৌণ ক্যোয়ারির ফলাফলগুলির বারবার ব্যবহার করে সেগুলি প্রায়শই (তবে সর্বদা নয়) এই ফলাফলগুলি ক্যাশে করে উপকার পেতে পারে। তবে যখন ক্যাচিং একটি সুবিধা হয় তখন মেমরি ব্যবহার করে বলেন ক্যাচিং অস্থায়ী টেবিলগুলি ব্যবহার করার কিছু সুবিধা রয়েছে।

পদ্ধতি

সাধারণত

আমি "শিরোনাম" ক্যোয়ারী (যেমন ProductIDগুলি পেতে , এবং একটি ক্ষেত্রে এছাড়াও নির্দিষ্ট বর্ণগুলি দিয়ে শুরু করার DaysToManufactureউপর ভিত্তি করে Name) "বিশদ" কোয়েরিগুলি (অর্থাত্ TransactionIDএস এবং TransactionDateএস প্রাপ্তি) থেকে আলাদা করেছি । ধারণাটি ছিল খুব সাধারণ প্রশ্নগুলি সম্পাদন করা এবং অপ্টিমাইজারটিকে তাদের সাথে যোগ দেওয়ার সময় বিভ্রান্ত হওয়ার অনুমতি না দেওয়া। স্পষ্টতই এটি সর্বদা সুবিধাজনক নয় কারণ এটি অপটিমাইজারটিকে ভাল, অনুকূলকরণ থেকেও অস্বীকার করে। কিন্তু যেমন আমরা ফলাফলগুলিতে দেখেছি, ক্যোয়ারির ধরণের উপর নির্ভর করে এই পদ্ধতিটির গুণাগুণ রয়েছে।

এই পদ্ধতির বিভিন্ন স্বাদের মধ্যে পার্থক্য হ'ল:

  • ধ্রুবক: পরামিতিগুলির পরিবর্তে যে কোনও প্রতিস্থাপনযোগ্য মান ইনলাইন স্থির হিসাবে জমা দিন। এটি ProductIDতিনটি পরীক্ষায় এবং টেস্ট 2-এ ফিরে আসা সারিগুলির সংখ্যা DaysToManufactureউল্লেখ করবে কারণ এটি "পণ্যের গুণাবলীর পাঁচগুণ" function এই উপ-পদ্ধতিটির অর্থ হ'ল প্রত্যেকে ProductIDতার নিজস্ব এক্সিকিউশন প্ল্যান পাবেন, যা উপাত্ত হতে পারে যদি এর জন্য ডেটা বিতরণে বিস্তৃত ভিন্নতা থাকে ProductID। তবে যদি ডেটা বিতরণে সামান্য তাত্পর্য হয় তবে অতিরিক্ত পরিকল্পনা উত্পন্ন করার ব্যয় সম্ভবত এটির পক্ষে উপযুক্ত হবে না।

  • প্যারামিটারাইজড: মৃত্যুদন্ড কার্যকর করার পরিকল্পনার ক্যাচিং এবং পুনরায় ব্যবহারের অনুমতি দিয়ে কমপক্ষে ProductIDহিসাবে জমা দিন @ProductID। টেস্ট 2-এ প্যারামিটার হিসাবে ফিরে আসার জন্য সারিগুলির পরিবর্তনশীল সংখ্যার সাথে চিকিত্সা করার জন্য একটি অতিরিক্ত পরীক্ষার বিকল্প রয়েছে।

  • অপরিবর্তিত অজানা:ProductID হিসাবে উল্লেখ করার সময় @ProductID, যদি ডেটা বিতরণে বিস্তৃত ভিন্নতা থাকে তবে এমন একটি পরিকল্পনাকে ক্যাশে করা সম্ভব যা অন্যান্য ProductIDমানগুলিতে নেতিবাচক প্রভাব ফেলে তাই এই কোয়েরি ইঙ্গিতটি কোনও ব্যবহার করে কিনা তা জানা ভাল।

  • ক্যাশে পণ্যগুলি:Production.Product প্রতিবার টেবিলটি জিজ্ঞাসা করার পরিবর্তে , ঠিক একই তালিকা পেতে একবারে ক্যোয়ারি চালান (এবং এটি উপস্থিত থাকাকালীন, টেবিলের ProductIDমধ্যে নেই এমন কোনও ফিল্টার আউট করুন TransactionHistoryযাতে আমরা কোনও অপচয় না করি don't সেখানে সংস্থানসমূহ) এবং ক্যাশে সেই তালিকা তালিকার DaysToManufactureক্ষেত্রটি অন্তর্ভুক্ত করা উচিত । এই অপশনটি ব্যবহার করে প্রথম সম্পাদনের জন্য লজিকাল রিডসে কিছুটা উচ্চতর প্রাথমিক আঘাত রয়েছে তবে এর পরে এটি কেবল TransactionHistoryসারণী যা অনুসন্ধান করা হয়েছে।

বিশেষভাবে

ঠিক আছে, তবে তাই, উম, কীভাবে সমস্ত সাব-কোয়েরিগুলিকে আলাদা আলাদা প্রশ্ন হিসাবে কোনও সূরসর ব্যবহার না করে এবং প্রতিটি ফলাফলকে অস্থায়ী টেবিল বা টেবিলের ভেরিয়েবলের জন্য সেট না করে পৃথক অনুসন্ধান হিসাবে ইস্যু করা সম্ভব? পরিষ্কারভাবে কর্সার / টেম্প টেবিল পদ্ধতিটি করা স্পষ্টভাবে প্রতিচ্ছবি পড়বে রিডস এবং রাইটসে। ওয়েল, এসকিউএলসিআরআর :) ব্যবহার করে। একটি এসকিউএলসিআরআর সঞ্চিত পদ্ধতি তৈরি করে আমি একটি ফলাফল সেট খুলতে সক্ষম হয়েছি এবং অবিচ্ছিন্ন ফলাফল সেট হিসাবে (এবং একাধিক ফলাফল সেট নয়) হিসাবে প্রতিটি সাব-কোয়েরির ফলাফলগুলি মূলত এটিতে প্রবাহিত করতে সক্ষম হয়েছি। পণ্য সম্পর্কিত তথ্য বাইরে (অর্থাত ProductID, NameএবংDaysToManufacture), সাব-কোয়েরির ফলাফলগুলির কোনও একটিও কোথাও (মেমরি বা ডিস্ক) সংরক্ষণ করা হয়নি এবং এসকিউএলসিআরআর স্টোরেজ পদ্ধতির মূল ফলাফল সেট হিসাবে পেরিয়ে গেছে। এটি আমাকে পণ্যের তথ্য পেতে একটি সাধারণ ক্যোয়ারী করার অনুমতি দেয় এবং তারপরে চক্রটি চালিয়ে, এর বিরুদ্ধে খুব সাধারণ প্রশ্নগুলি জারি করে TransactionHistory

এবং, এজন্য আমাকে পরিসংখ্যানগুলি ক্যাপচার করতে এসকিউএল সার্ভার প্রোফাইলার ব্যবহার করতে হয়েছিল। এসকিউএলসিআরআর সঞ্চিত পদ্ধতিটি "প্রকৃত কার্যনির্বাহী পরিকল্পনা অন্তর্ভুক্ত করুন" অনুসন্ধান বিকল্পটি সেট করে অথবা জারি করে কোনও কার্যকরকরণ পরিকল্পনা ফেরত দেয় না SET STATISTICS XML ON;

পণ্য তথ্য ক্যাশে করার জন্য, আমি একটি readonly staticজেনেরিক তালিকা ব্যবহার করেছি (উদাহরণস্বরূপ _GlobalProductsনীচের কোডে)। মনে হচ্ছে যে সংগ্রহগুলি যোগ লঙ্ঘন করে না readonlyবিকল্প, অত: পর এই কোড কাজ করে সমাবেশ টি PERMISSON_SETএর SAFE:), যে এমনকি যদি পাল্টা স্বজ্ঞাত।

উত্পন্ন উত্সগুলি

এই এসকিউএলসিআরআর সঞ্চিত পদ্ধতি দ্বারা উত্পন্ন প্রশ্নগুলি নীচে রয়েছে:

পণ্যর বিবরণ

পরীক্ষার নম্বর 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);
            }
        }


    }
}

টেস্ট প্রশ্ন

এখানে পরীক্ষাগুলি পোস্ট করার মতো পর্যাপ্ত জায়গা নেই তাই আমি অন্য একটি জায়গা খুঁজে পাব।

উপসংহার

নির্দিষ্ট পরিস্থিতিতে, এসকিউএলসিআরআর টি-এসকিউএল-তে করা যায় না এমন প্রশ্নের কয়েকটি দিক ম্যানিপুলেট করতে ব্যবহার করা যেতে পারে। টেম্প টেবিলের পরিবর্তে ক্যাশিংয়ের জন্য মেমরি ব্যবহার করার ক্ষমতা রয়েছে, যদিও স্মৃতি মেমরিটি স্বয়ংক্রিয়ভাবে সিস্টেমে ফিরে আসে না বলে এটি খুব কম এবং সাবধানতার সাথে করা উচিত। এই পদ্ধতিটি এমন কোনও বিষয় নয় যা অ্যাডহক কোয়েরিগুলিকে সহায়তা করবে, যদিও এখানে সম্পাদন করা প্রশ্নের আরও বেশি দিকগুলি উপস্থাপনের জন্য প্যারামিটারগুলি যুক্ত করে আমি কেবল এখানে দেখানোর চেয়ে আরও নমনীয় করে তোলা সম্ভব।


হালনাগাদ

অতিরিক্ত পরীক্ষা
আমার মূল পরীক্ষাগুলিতে 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 ফলাফল-সহকারী সূচক সহ (ট্রানজেকশনআইডি ডিইএসসি সহ)
উপরের ফলাফলগুলি স্ট্যান্ডার্ড, নন-ক্যাচিং পরীক্ষার জন্য। এবার CROSS APPLY, কেবল সিটিইকে পরাজিত করে না (ঠিক যেমন অ্যারোন টেস্ট নির্দেশিত), তবে এসকিউএলসিআরআর প্রোগ 30 রিডস দ্বারা নেতৃত্ব নিয়েছে (উও হু)।

টেস্ট 3 ফলাফল-সহকারী সূচক সহ (ট্রানজেকশনআইডি ডিইএসসি সহ) এবং ক্যাচিং
উপরের ফলাফলগুলি ক্যাশিং সক্ষম করার সাথে পরীক্ষার জন্য। এবার সিটিইর পারফরম্যান্স হ্রাস পায় না, তবুও এটি CROSS APPLYমারধর করে। যাইহোক, এখন এসকিউএলসিআরআর প্রোগ 23 রিডের নেতৃত্বে নেয় (আবারও হু, হু)।

অ্যাভয়েস নিন

  1. ব্যবহার করার বিভিন্ন বিকল্প রয়েছে। তাদের প্রত্যেকের শক্তি রয়েছে বলে কয়েকটি চেষ্টা করা ভাল। এখানে করা পরীক্ষাগুলি সমস্ত পরীক্ষার জুড়ে সেরা এবং সবচেয়ে খারাপ অভিনেতা (একটি সমর্থনকারী সূচক সহ) এর মধ্যে পঠন এবং সময় উভয় ক্ষেত্রেই একটি ছোট পার্থক্য দেখায়; Reads এর প্রকরণটি প্রায় 350 এবং সময়কাল 55 এমএস। এসকিউএলসিআরআর প্রোক 1 টি ছাড়া সমস্ত পরীক্ষায় জিতেছে (পড়ার শর্তাবলী), কেবলমাত্র কয়েকটি পাঠ সংরক্ষণ করা এসকিউএলসিএলআর রুটে যাওয়ার রক্ষণাবেক্ষণ ব্যয়ের পক্ষে উপযুক্ত নয়। তবে অ্যাডভেঞ্চার ওয়ার্কস ২০১২-এ, টেবিলটিতে Productকেবল 504 টি সারি রয়েছে এবং TransactionHistoryকেবল 113,443 সারি রয়েছে। এই পদ্ধতিগুলির পারফরম্যান্সের পার্থক্যটি সম্ভবত সারি সংখ্যা বাড়ার সাথে আরও প্রকট হয়ে উঠবে।

  2. এই প্রশ্নটি সারিগুলির একটি নির্দিষ্ট সেট পাওয়ার জন্য নির্দিষ্ট ছিল, তবে এটি উপেক্ষা করা উচিত নয় যে পারফরম্যান্সের একক বৃহত্তম ফ্যাক্টরটি নির্দিষ্ট এসকিউএল নয়। কোন পদ্ধতিটি সত্যই সেরা তা নির্ধারণ করার আগে একটি ভাল সূচকের জায়গা হওয়া দরকার।

  3. এখানে পাওয়া সবচেয়ে গুরুত্বপূর্ণ পাঠটি ক্রস অ্যাপ্লাই বনাম সিটিই বনাম এসকিউএলসিআরআর সম্পর্কিত নয়: এটি পরীক্ষা সংক্রান্ত T ধরে নেই। বেশ কয়েকটি লোকের কাছ থেকে ধারণা পান এবং যতটা সম্ভব পরিস্থিতি পরীক্ষা করুন।


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 |
+-------+-----------+

মানগুলি পৃথক হতে পারে যেখানে এটি অর্জন করতে আপনাকে নিজের সিটিইতে রাজ্যের টেবিলের সাথে এটির মতো যোগ দিতে হবে:

SELECT [CustomerID]
    ,[OrderDate]
    ,[TotalAmt]
    ,[State]
FROM GiveMeCounts gmc
INNER JOIN StateTable st ON gmc.[State] = st.[State]
    AND gmc.MySeqNum <= st.MaxSeqNum
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.